在J2SE5.0后推出了自動裝箱和拆箱的功能,以提高我們的開發(fā)效率,然而自動裝箱和拆箱實際上是通過編譯器來支持的(并非語言本身,或者說虛擬機),因而這種支持也隱藏了部分內(nèi)部實質(zhì),再加上某些類的優(yōu)化(比如Integer里面的緩存等,參看關(guān)于緩存節(jié)),就更加容易在特定的環(huán)境下產(chǎn)生問題,并且如果不知道原來還無法調(diào)試。以下先是簡單的介紹了編譯器對裝箱和拆箱的實現(xiàn),并根據(jù)實現(xiàn)簡單介紹一下可能會遇到的幾個問題。
裝箱和拆箱實現(xiàn)
以下裝箱和拆箱代碼:
int intValue = (Integer)value;
Integer newIntValue = new Integer(10);
編譯成字節(jié)碼如下:
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]
從以上字節(jié)碼可以看到10首先調(diào)用valueOf方法轉(zhuǎn)換為Integer實例,再賦值該value,而value強制轉(zhuǎn)換成Integer類后,會調(diào)用intValue方法,后賦值給intValue。這就是用編譯器來實現(xiàn)裝箱和拆箱。
奇怪的NullPointerException
查看以下代碼:
int intValue = value;
可以編譯通過,但是運行的時候卻會發(fā)生NullPointerException。這是由什么引起的呢?依然看一下字節(jié)碼就可以了:
0 aconst_null
1 astore_1 [value]
2 aload_1 [value]
3 invokevirtual java.lang.Integer.intValue() : int [20]
6 istore_2 [intValue]
從字節(jié)碼中可以看到,從value賦值該intValue事實上是直接在value實例上調(diào)用intValue函數(shù)。
對當前代碼,我們可以一眼就看出當前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));
這段代碼會是什么結(jié)果?
value1 == value2 is true
value3 == value4 is false
兩段代碼就是值不一樣,其他的都一樣,竟然會有區(qū)別?這個奧妙就因為裝箱過程中調(diào)用的是valueOf方法,而valueOf方法對值在-128到127之間的數(shù)值緩存了(參見關(guān)于緩存一節(jié)),因而value1和value2的引用是相同的,而value3和value4的引用是不一樣的,而==比較的是引用,因而才會出現(xiàn)以上的結(jié)果。
這確的做法應該是:
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)));
這樣的結(jié)果就是預料的結(jié)果了:
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));
執(zhí)行結(jié)果:
str1 == str2 is true
str3 == str4 is false
這是因為str1和str2使用的是同一個字符串,即在字符常量中的字符串,而str3和str4在使用字符常量中的字符為參數(shù)又創(chuàng)建出了兩個新的字符串對象,因而在引用比較情況下是不等的。我們可以從字節(jié)碼中得到這些信息(刪除打印的代碼):
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]
正確的做法還是調(diào)用equals方法,而不是使用“==”操作符。
關(guān)于緩存
據(jù)目前信息,有緩存的類有:Byte、Short、Integer、Long以及Boolean類。而這種緩存也只是在調(diào)用valueOf(靜態(tài))方法的時候才會存在(裝箱正是調(diào)用了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);
}
2011-01-05