自动装箱和拆箱的一些问题(IntegerCache的范围等)

什么是装箱?什么是拆箱?

自动装箱拆箱操作是从Java SE5开始提供的特性。

  • 装箱:将基本类型用它们对应的引用类型包装起来;

  • 拆箱:将包装类型转换为基本数据类型;

Java SE5之前,需要这样创建数值为10的Integer对象。

Integer i = new Integer(10);

加入自动装箱拆箱后则:

Integer i = 10;
Integer i = 10;  //装箱
int n = i;   //拆箱

8种基本数据类型和对应的包装器类型。

int(4字节)Integer
byte(1字节)Byte
short(2字节)Short
long(8字节)Long
float(4字节)Float
double(8字节)Double
char(2字节)Character
boolean(未定)Boolean

自动装箱和拆箱中的问题

public class Main {
    public static void main(String[] args) {
         
        Integer i1 = 100;
        Integer i2 = 100;
        Integer i3 = 200;
        Integer i4 = 200;
         
        System.out.println(i1==i2);
        System.out.println(i3==i4);
    }
}

输出为:

true
false

导致这个问题的原因是i1和i2指向同一个Integer对象,而i3和i4指向不同的Integer对象,在用==进行比较的过程中,使用了Integer.valueOf方法,其源码是这样的:

public static Integer valueOf(int i) {
        if(i >= -128 && i <= IntegerCache.high)
            return IntegerCache.cache[i + 128];
        else
            return new Integer(i);
    }

基本类型的包装类的大部分都实现了常量池技术,可以看到这里对i也就是传入的int数值进行了范围限定,在传入数值在-128~IntegerCache.high之间时,不会创建新的Integer对象,而是直接指向cache中存在的对象。再来看看IntegerCache的源码:

private static class IntegerCache {
        static final int high;
        static final Integer cache[];

        static {
            final int low = -128;

            // high value may be configured by property
            int h = 127;
            if (integerCacheHighPropValue != null) {
                // Use Long.decode here to avoid invoking methods that
                // require Integer's autoboxing cache to be initialized
                int i = Long.decode(integerCacheHighPropValue).intValue();
                i = Math.max(i, 127);
                // Maximum array size is Integer.MAX_VALUE
                h = Math.min(i, Integer.MAX_VALUE - -low);
            }
            high = h;

            cache = new Integer[(high - low) + 1];
            int j = low;
            for(int k = 0; k < cache.length; k++)
                cache[k] = new Integer(j++);
        }

        private IntegerCache() {}
    }

可以看到这里允许我们对high即上限进行重写,但是不允许对low进行变化,默认为[-128,127]之间,所以上述代码输出一个为true一个为false。因为2进制最大数是127:是2的7次方减1,也就是127 , 而最小的数是-128:由于负数在计算机中是以其补码形式存在也就是这里是根据二进制的大小存在的。这是对内存优化的机制。

由此可以猜想,Double类型(包含Float),Boolean类型,他们的输出会怎样的。

public class Main {
    public static void main(String[] args) {
         
        Double i1 = 100.0;
        Double i2 = 100.0;
        Double i3 = 200.0;
        Double i4 = 200.0;
         
        System.out.println(i1==i2);
        System.out.println(i3==i4);
    }
}
false
false

可以想象,Integer类型(包括Short,Long等)他们的valueOf方法的实现是类似的,而浮点数的个数由于是无限的,所以不能采用这样的形式来优化,因此Double,Float会直接创建两个不同对象。而Boolean呢?

public class Main {
    public static void main(String[] args) {
         
        Boolean i1 = false;
        Boolean i2 = false;
        Boolean i3 = true;
        Boolean i4 = true;
         
        System.out.println(i1==i2);
        System.out.println(i3==i4);
    }
}
true
true

查看他的源代码发现:

public static Boolean valueOf(boolean b) {
        return (b ? TRUE : FALSE);
    }
public static final Boolean TRUE = new Boolean(true);

    /** 
     * The <code>Boolean</code> object corresponding to the primitive 
     * value <code>false</code>. 
     */
    public static final Boolean FALSE = new Boolean(false);`
Boolean类型相当于直接将truefalse的值直接传递过去了。

Integer i = new Integer(xxx)和Integer i =xxx;这两种方式的区别。

  1. 第一种方式不会触发自动装箱的过程;而第二种方式会触发;
  2. 在执行效率和资源占用上的区别。第二种方式的执行效率和资源占用在一般性情况下要优于第一种情况(注意这并不是绝对的)。

需要注意的是,当 "=="运算符的两个操作数都是 包装器类型的引用,则是比较指向的是否是同一个对象,而如果其中有一个操作数是表达式(即包含算术运算)则比较的是数值(即会触发自动拆箱的过程)。另外,对于包装器类型,equals方法并不会进行类型转换。

参考:https://www.cnblogs.com/dolphin0520/p/3780005.html

<think>我们正在讨论Java中的自动装箱拆箱,特别是在List<Integer>的上下文中。根据用户的问题,我们需要解释自动装箱拆箱的原理以及使用方法,并结合提供的引用材料。 首先,自动装箱(Autoboxing)是指将基本数据类型自动转换为对应的包装类对象,而自动拆箱(Unboxing)则是将包装类对象自动转换为基本数据类型。这在Java中是为了方便在需要对象的地方使用基本数据类型,例如在集合中(因为集合只能存储对象,不能存储基本数据类型)。 在List<Integer>中,当我们添加一个int基本类型时,Java会自动将其装箱为Integer对象;当我们从列表中取出Integer对象并赋值给int基本类型时,会自动拆箱。 引用[1]中的代码示例: ```java List<Integer> list = Arrays.asList(1,2,3,4); int sum = 0; for(int i:list) sum +=i; ``` 这里,`Arrays.asList(1,2,3,4)`中的每个整数都会被自动装箱为Integer对象。在for-each循环中,每次迭代将Integer对象从列表中取出,然后自动拆箱为int类型,以便进行累加。 原理: - 自动装箱:当我们将基本数据类型传递给需要对象的方法或赋值给对象引用时,编译器会自动调用对应包装类的valueOf方法(例如Integer.valueOf(int))来创建对象。 - 自动拆箱:当我们需要将包装类对象转换为基本数据类型时,编译器会自动调用对应包装类的xxxValue方法(例如Integer.intValue())来获取基本数据类型的值。 引用[2][3]提到了自动装箱拆箱的一些陷阱: 1. 包装类对象的比较:使用`==`比较两个包装类对象时,比较的是对象的地址。对于Integer,在-128到127之间的整数,由于IntegerCache的存在,会返回缓存的对象,因此在这个范围内的相同整数使用`==`比较会返回true,超出这个范围则返回false(因为每次装箱都会创建新的对象)。所以,在比较包装类对象时,应该使用equals方法。 2. 在遇到算术运算时,包装类对象会自动拆箱。例如,在表达式`c == (a + b)`中,ab会先拆箱,然后相加,再与c拆箱后的值比较(数值比较)。 3. 包装类的equals方法不会处理类型转换。例如,在`g.equals(a+b)`中,a+b是int类型,然后被装箱为Integer,而g是Long类型,所以equals方法会先检查类型,类型不同直接返回false。 使用方法: - 在集合中使用基本数据类型的包装类(如List<Integer>)时,可以像使用基本数据类型一样进行添加获取,自动装箱拆箱会隐式进行。 - 但要注意比较操作,应使用equals方法而不是`==`。 - 注意在混合类型表达式中(如LongInteger一起运算),会自动拆箱为基本类型进行运算,但重新赋值给包装类时要注意类型。 现在,我们结合用户的问题,给出一个清晰的解释示例。 用户问题:理解Java中List<Integer>的自动装箱自动拆箱机制,包括原理及使用方法。 回答结构: 1. 解释自动装箱拆箱的概念。 2. 说明在List<Integer>中的具体表现。 3. 给出示例代码并解释。 4. 指出注意事项(陷阱)。 同时,根据指令,我们需要生成相关问题。 参考引用: [^1] 展示了自动装箱拆箱在List<Integer>中的使用。 [^2] 详细说明了自动装箱拆箱的陷阱。 [^3] 提到类型擦除自动装箱是语法糖。 [^4] 提到了包装类的一些属性集合的使用。 因此,我们可以这样组织回答:</think>### Java List<Integer> 自动装箱拆箱机制详解 #### 一、核心原理 1. **自动装箱(Autoboxing)** 当基本数据类型(如 `int`)需要作为对象(如 `Integer`)使用时,编译器自动将基本类型转换为对应的包装类对象: ```java List<Integer> list = new ArrayList<>(); list.add(5); // 等价于 list.add(Integer.valueOf(5)); ``` 此处 `int` 值 `5` 被自动装箱为 `Integer` 对象[^1][^3]。 2. **自动拆箱(Unboxing)** 当包装类对象需要作为基本类型使用时,编译器自动将包装类对象转换为基本类型: ```java int first = list.get(0); // 等价于 int first = list.get(0).intValue(); ``` 从 `List<Integer>` 获取元素时自动拆箱为 `int`[^1][^3]。 #### 二、在 List<Integer> 中的工作流程 ```java List<Integer> numbers = Arrays.asList(10, 20, 30); // 自动装箱:int → Integer int sum = 0; for (int num : numbers) { // 循环中自动拆箱:Integer → int sum += num; // 直接使用基本类型运算 } ``` - **添加元素**:所有 `int` 值自动装箱为 `Integer` 对象存储 - **遍历操作**:迭代时自动拆箱为 `int`,支持基本类型运算[^1][^4] #### 三、关键注意事项 1. **性能影响** 频繁装箱/拆箱会产生额外对象开销(如循环内操作),可能影响性能: ```java // 低效示例:循环中重复装箱 for (int i = 0; i < 10000; i++) { list.add(i); // 每次循环都新建 Integer 对象 } ``` 2. **对象比较陷阱** 包装类对象比较需使用 `equals()` 而非 `==`: ```java Integer a = 200; Integer b = 200; System.out.println(a == b); // false(对象地址不同) System.out.println(a.equals(b)); // true(值相同) ``` 注意 `Integer` 缓存范围:`-128~127` 会复用对象[^2]。 3. **空指针风险** 自动拆箱时若对象为 `null` 会抛出异常: ```java List<Integer> list = new ArrayList<>(); list.add(null); int num = list.get(0); // 抛出 NullPointerException ``` #### 四、最佳实践建议 1. 在集合操作中直接使用 `Integer` 避免频繁转换: ```java // 推荐:减少拆箱次数 Integer sum = list.stream().reduce(0, Integer::sum); ``` 2. 比较数值时显式拆箱: ```java if (num1.intValue() == num2.intValue()) { ... } ``` 3. 批量操作优先使用基本类型数组: ```java int[] primitiveArray = list.stream().mapToInt(Integer::intValue).toArray(); ``` #### 五、底层实现机制 - 编译时通过语法糖实现,字节码中插入 `Integer.valueOf()` `intValue()` 调用[^3] - 泛型擦除后 `List<Integer>` 实际存储 `Object` 类型,装箱确保类型安全[^3] > 自动装箱/拆箱是编译器提供的语法糖,本质是编译器自动插入包装/解包方法调用[^3]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值