在J2SE5.0后推出了自动装箱和拆箱的功能,以提高我们的开发效率,然而自动装箱和拆箱实际上是通过编译器来支持的(并非语言本身,或者说虚拟机),因而这种支持也隐藏了部分内部实质,再加上某些类的优化(比如Integer里面的缓存等,参看关于缓存节),就更加容易在特定的环境下产生问题,并且如果不知道原来还无法调试。以下先是简单的介绍了编译器对装箱和拆箱的实现,并根据实现简单介绍一下可能会遇到的几个问题。
装箱和拆箱实现
以下装箱和拆箱代码:
Objectvalue=10;intintValue=(Integer)value;IntegernewIntValue=newInteger(10);
编译成字节码如下:
0bipush 10
2invokestatic java.lang.Integer.valueOf(int): java.lang.Integer [20]
5astore_1 [value]
6aload_1 [value]
7checkcast java.lang.Integer [21]
10invokevirtual java.lang.Integer.intValue(): int [26]
13istore_2 [intValue]
14new java.lang.Integer [21]
17dup
18bipush 10
20invokespecial java.lang.Integer(int) [30]
23astore_3 [newIntValue]
从以上字节码可以看到10首先调用valueOf方法转换为Integer实例,再赋值该value,而value强制转换成Integer类后,会调用intValue方法,后赋值给intValue。这就是用编译器来实现装箱和拆箱。
奇怪的NullPointerException
查看以下代码:
Integervalue=null;intintValue=value;
可以编译通过,但是运行的时候却会发生NullPointerException。这是由什么引起的呢?依然看一下字节码就可以了:
0aconst_null
1astore_1 [value]
2aload_1 [value]
3invokevirtual java.lang.Integer.intValue(): int [20]
6istore_2 [intValue]
从字节码中可以看到,从value赋值该intValue事实上是直接在value实例上调用intValue函数。
对当前代码,我们可以一眼就看出当前value是null的问题,但是如果这个null是在很远以外的地方赋值的呢?或者是间接赋值呢?这个时候遇到这种问题就会比较诡异了。
相等与不相等问题
查看一下代码:
Integervalue1=100;Integervalue2=100;System.out.println("value1==value2is"+(value1==value2));Integervalue3=200;Integervalue4=200;System.out.println("value3==value4is"+(value3==value4));
这段代码会是什么结果?
value1 == value2 istrue
value3 == value4 isfalse
两段代码就是值不一样,其他的都一样,竟然会有区别?这个奥妙就因为装箱过程中调用的是valueOf方法,而valueOf方法对值在-128到127之间的数值缓存了(参见关于缓存一节),因而value1和value2的引用是相同的,而value3和value4的引用是不一样的,而==比较的是引用,因而才会出现以上的结果。
这确的做法应该是:
Integervalue1=100;Integervalue2=100;System.out.println("value1==value2is"+(value1.equals(value2)));Integervalue3=200;Integervalue4=200;
System.out.println("value3==value4is"+(value3.equals(value4)));
这样的结果就是预料的结果了:
value1 == value2 is true
value3 == value4 is true
所以我们要慎用“==”操作符。
String中的相等与不等
在String中也有类似的情况,查看一下代码:
Stringstr1="abc";Stringstr2="abc";System.out.println("str1==str2is"+(str1==str2));Stringstr3=newString("abc");Stringstr4=newString("abc");System.out.println("str3==str4is"+(str3==str4));
执行结果:
str1 == str2 istrue
str3 == str4 isfalse
这是因为str1和str2使用的是同一个字符串,即在字符常量中的字符串,而str3和str4在使用字符常量中的字符为参数又创建出了两个新的字符串对象,因而在引用比较情况下是不等的。我们可以从字节码中得到这些信息(删除打印的代码):
0ldc <String "abc"> [20]
2astore_1 [str1]
3ldc <String "abc"> [20]
5astore_2 [str2]
6new java.lang.String [22]
9dup
10ldc <String "abc"> [20]
12invokespecial java.lang.String(java.lang.String)[24]
15astore_3 [str3]
16new java.lang.String [22]
19dup
20ldc <String "abc"> [20]
22invokespecial java.lang.String(java.lang.String) [24]
25astore 4 [str4]
正确的做法还是调用equals方法,而不是使用“==”操作符。
关于缓存
据目前信息,有缓存的类有:Byte、Short、Integer、Long以及Boolean类。而这种缓存也只是在调用valueOf(静态)方法的时候才会存在(装箱正是调用了valueOf方法)。对整型,缓存的值都是-128到127(包括-128和127)之间,其他值都不缓存,而对Boolean类型只有true和false值。代码如下:
publicfinalclassIntegerextendsNumber{publicstaticIntegervalueOf(inti){if(i>=-128&&i<=IntegerCache.high)returnIntegerCache.cache[i+128];elsereturnnewInteger(i);}publicfinalclassBoolean{publicstaticBooleanvalueOf(booleanb){return(b?TRUE:FALSE);}人只要不失去方向,就不会失去自己