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