前言

自动装箱和拆箱从Java 1.5开始引入,目的是让原始类型值与对应的包装对象之间可以自动的相互转换,比如将int类型值转换成Integer对象称为装箱,反之将Integer对象转换成int类型值称为拆箱。因为这里的装箱和拆箱是自动进行的而非人为转换,所以就称为自动装箱和拆箱。

在Java中,原始类型byteshortcharintlongfloatdoubleboolean分别对应的包装类为ByteShortCharacterIntegerLongFloatDoubleBoolean

实现原理

自动装箱与拆箱其实是编译器自动为我们调用了相关方法,自动装箱是通过调用包装类的valueOf()方法实现的,而自动拆箱是通过调用包装类的xxxValue()方法实现的(xxx代表对应的基本数据类型)。

何时发生自动装箱和拆箱

自动装箱和拆箱主要发生在两种情况,一种是赋值时,另一种是在方法调用时。

赋值时

这是最常见的一种情况,在Java 1.5以前我们需要手动地进行转换才行,而现在所有的转换都是由编译器来完成。

1
2
3
4
5
6
7
//before autoboxing
Integer iObject = Integer.valueOf(3);
int iPrimitive = iObject.intValue()

//after java5
Integer iObject = 3; //autobxing - primitive to wrapper conversion
int iPrimitive = iObject; //unboxing - object to primitive conversion

方法调用时

这是另一个常用的情况,当我们在方法调用时,我们可以传入原始数据值或者对象,同样编译器会帮我们进行转换。

1
2
3
4
5
6
7
8
public static Integer show(Integer iParam){
System.out.println("autoboxing example - method invocation i: " + iParam);
return iParam;
}

//autoboxing and unboxing in method invocation
show(3); //autoboxing
int result = show(3); //unboxing because return type of method is Integer

show方法接受Integer对象作为参数,当调用show(3)时,会将int值转换成对应的Integer对象,这就是所谓的自动装箱,show方法返回Integer对象,而int result = show(3);resultint类型,所以这时候发生自动拆箱操作,将show方法的返回的Integer对象转换成int值。

需要注意的问题

自动装箱与拆箱虽然为我们省下了很多不必要的工作,使代码更加简洁清晰,但是如果使用不当,则会引起性能问题。

循环中的自动装箱

自动装箱有一个问题,那就是在一个循环中进行自动装箱操作的情况,如下面的例子就会创建多余的对象,影响程序的性能:

1
2
3
4
Integer sum = 0;
for(int i=1000; i<5000; i++){
sum+=i;
}

上面的代码sum+=i可以看成sum = sum + i,但是+这个操作符不适用于Integer对象,首先sum进行自动拆箱操作,进行数值相加操作,最后发生自动装箱操作转换成Integer对象。其内部变化如下:

1
2
int result = sum.intValue() + i;
Integer sum = Integer.valueOf(result);

由于我们这里声明的sumInteger类型,在上面的循环中会创建将近4000个无用的Integer对象,在这样庞大的循环中,会降低程序的性能并且加重了垃圾回收的工作量。因此在我们编程时,需要注意到这一点,正确地声明变量类型,避免因为自动装箱引起的性能问题。

对象和原始类型值的比较

包装类对象和原始类型值的比较是很容易出错的一个地方,需要注意的是,==可以用于原始值的比较,也可以用于对象的比较,但是用于对象之间的比较时,比较的不是对象代表的值,而是检查两个对象是否是同一对象。而当其中一个操作数是原始类型值或算术运算时,则比较的是数值(触发自动拆箱)。以下几个例子基本可以涵盖所有情况:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

public class Main {
    public static void main(String[] args) {
        Integer a = 1;
        Integer b = 2;
        Integer c = 3;
        Integer d = 3;
        Integer e = 321;
        Integer f = 321;
        Long g = 3L;
        Long h = 2L;
         
        System.out.println(c==d); // 比较对象是否为同一个,因为 Integer 会缓存-128~127之间的对象
        System.out.println(e==f); // 比较对象是否为同一个,数值在缓存之外
        System.out.println(c==(a+b)); // a+b运算触发自动拆箱,之后数值比较
        System.out.println(c.equals(a+b)); // a+b运算触发自动拆箱,之后自动装箱
        System.out.println(g==(a+b)); // a+b运算触发自动拆箱,之后数值比较
        System.out.println(g.equals(a+b)); // a+b运算触发自动拆箱,之后自动装箱,由于不是同一类型,equals() 返回 false
        System.out.println(g.equals(a+h)); // a+h运算触发自动拆箱,int类型晋升为long,之后自动装箱为Long,equals() 返回 true
    }
}

其运行结果如下:

1
2
3
4
5
6
7
true
false
true
true
true
false
true

参考资料