2025-05-23

在J2SE5.0後推出瞭自動裝箱和拆箱的功能,以提高我們的開發效率,然而自動裝箱和拆箱實際上是通過編譯器來支持的(並非語言本身,或者說虛擬機),因而這種支持也隱藏瞭部分內部實質,再加上某些類的優化(比如Integer裡面的緩存等,參看關於緩存節),就更加容易在特定的環境下產生問題,並且如果不知道原來還無法調試。以下先是簡單的介紹瞭編譯器對裝箱和拆箱的實現,並根據實現簡單介紹一下可能會遇到的幾個問題。

裝箱和拆箱實現
以下裝箱和拆箱代碼:


       Object value = 10;
       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
查看以下代碼:


       Integer value = null;
       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 value1 = 100;
       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 value1 = 100;
       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 str1 = "abc";
       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 final class Integer extends Number {

    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);
    }

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *