一、什么是装箱和拆箱?
Java为八种基本数据类型都提供了对应的包装器类型,至于为什么会为每种基本数据类型提供包装器类型,我的理解是:基本数据类型只是单个值,并不是对象,没有可调用的方法;而包装类是类,有许多的静态方法可供调用,效率高。
在Java SE5之前,如果要生成一个数值为10的Integer对象,必须这样进行:
1
|
Integer i =
new
Integer(
10
);
|
而在从Java SE5开始就提供了自动装箱的特性,如果要生成一个数值为10的Integer对象,只需要这样就可以了:
1
|
Integer i =
10
;
|
这个过程中会自动根据数值创建对应的 Integer对象,这就是装箱。
那什么是拆箱呢?顾名思义,它其实就是装箱的逆过程:
1
2
|
Integer i =
10
;
//装箱
int
n = i;
//拆箱
|
总的来说,装箱就是:自动将基本数据类型转换为包装器类型;拆箱就是:自动将包装器类型转换为基本数据类型。
下表是基本数据类型对应的包装器类型:
二、装箱和拆箱是如何实现的
我们就以Interger类为例,下面看一段代码:
1
2
3
4
5
6
|
public
class
Main {
public
static
void
main(String[] args) {
Integer i =
10
;
int
n = i;
}
}
|
反编译class文件之后得到如下内容:
从反编译得到的字节码内容可以看出,在装箱的时候自动调用的是Integer的valueOf(int)方法。而在拆箱的时候自动调用的是Integer的intValue方法。其他的也类似,比如Double、Character。
因此可以用一句话总结装箱和拆箱的实现过程:
装箱过程是通过调用包装器的valueOf方法实现的,而拆箱过程是通过调用包装器的 xxxValue方法实现的。(xxx代表对应的基本数据类型)
三、易混淆的问题
1.下面这段代码的输出结果是什么?
1
2
3
4
5
6
7
8
9
10
11
12
|
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);
}
}
|
也许有些朋友会说都会输出false,或者也有朋友会说都会输出true。但是事实上输出结果是:
true false
为什么会出现这样的结果?输出结果表明i1和i2指向的是同一个对象,而i3和i4指向的是不同的对象。此时只需一看源码便知究竟,下面这段代码是Integer的valueOf方法的具体实现:
public static Integer valueOf(int i) { if(i >= -128 && i <= IntegerCache.high) return IntegerCache.cache[i + 128]; else return new Integer(i); }
而其中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() {} }
从这2段代码可以看出,在通过valueOf方法创建Integer对象的时候,如果数值在[-128,127]之间,便返回指向IntegerCache.cache中已经存在的对象的引用(指的是静态的已经创建好了的Integer数组对象);否则创建一个新的Integer对象。
上面的代码中i1和i2的数值为100,因此会直接从cache中取已经存在的对象,所以i1和i2指向的是同一个对象,而i3和i4则是分别指向不同的对象。
2.下面这段代码的输出结果是什么?
1
2
3
4
5
6
7
8
9
10
11
12
|
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不一样,是因为它们的valueOf实现不一样。对于Integer,在(-128,128]之间只有固定的256个值,所以为了避免多次创建对象,我们事先就创建好一个大小为256的Integer数组SMALL_VALUES,所以如果值在这个范围内,就可以直接返回我们事先创建好的对象就可以了。对于Double,我们就不能这样做,因为它在这个范围内个数是无限的。 总结一句就是:在某个范围内的整型数值的个数是有限的,而浮点数却不是。所以在Double里面的做法很直接,就是直接创建一个对象,所以每次创建的对象都不一样。
注意,Integer、Short、Byte、Character、Long这几个类的valueOf方法的实现是类似的。
Double、Float的valueOf方法的实现是类似的。
3.下面这段代码输出结果是什么:
1
2
3
4
5
6
7
8
9
10
11
12
|
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
可以看到返回的都是true,也就是它们执行valueOf返回的都是相同的对象。下面是Boolean的valueOf方法的具体实现:
public static Boolean valueOf(boolean b) { return (b ? TRUE : FALSE); }
其中的 TRUE 和FALSE又是什么呢?它们是在内部已经提前创建好两个对象(静态成员属性),因为它只有两种情况,这样也是为了避免重复创建太多的对象。
public static final Boolean TRUE = new Boolean(true); public static final Boolean FALSE = new Boolean(false);
4.下面程序的输出结果是什么?
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);
System.out.println(e==f);
System.out.println(c==(a+b));
System.out.println(c.equals(a+b));
System.out.println(g==(a+b));
System.out.println(g.equals(a+b));
System.out.println(g.equals(a+h));
}
}
|
先别看输出结果,读者自己想一下这段代码的输出结果是什么。这里面需要注意的是:当 "=="运算符的两个操作数都是 包装器类型的引用,则是比较指向的是否是同一个对象,而如果其中有一个操作数是表达式(即包含算术运算)则比较的是数值(即会触发自动拆箱的过程)。另外,对于包装器类型,equals方法并不会进行类型转换。明白了这2点之后,上面的输出结果便一目了然:
true false true true true false true
第一个和第二个输出结果没有什么疑问。第三句由于 a+b包含了算术运算,因此会触发自动拆箱过程(会调用intValue方法),因此它们比较的是数值是否相等。而对于c.equals(a+b)会先触发自动拆箱过程,再触发自动装箱过程,也就是说a+b,会先各自调用intValue方法,得到了加法运算后的数值之后,便调用Integer.valueOf方法,再进行equals比较。同理对于后面的也是这样,不过要注意倒数第二个和最后一个输出的结果(如果数值是int类型的,装箱过程调用的是Integer.valueOf;如果是long类型的,装箱调用的Long.valueOf方法)。
如果对上面的具体执行过程有疑问,可以尝试获取反编译的字节码内容进行查看。
四、总结
1、什么时候会引发装箱和拆箱。
2、理解equal() 与 "==" 的区别。
3、自动装箱会隐式地创建对象,这样会增加GC压力,拉低程序的性能。所以要尽量避免引入不必要的自动装箱操作。(关于GC可详见本博客另一篇 文章Java垃圾回收机制)
本文参考自: http://www.cnblogs.com/dolphin0520/p/3780005.html
http://blog.youkuaiyun.com/hp910315/article/details/48654777