Java自動裝箱的陷阱


Java的自動拆箱和裝箱是Java語言的一顆語法糖。在之前的學習中有很多誤解,在別人的幫助下作出一些修正。先看下面的代碼:

 1 public static void main(String args[]) {
 2         Integer a = 1;
 3         Integer b = 2;
 4         Integer c = 3;
 5         Integer d = 3;
 6         Integer e = 321;
 7         Integer f = 321;
 8         Long g = 3L;
 9         int x = 3;
10         long y = 3L;
11         
12         //x,y雖然類型不同但是可以直接進行數值比較
13         System.out.println(x == y);
14         //System.out.println(c == g); 提示出錯,不可比較的類型。說明此時沒有自動拆箱
15         System.out.println(c == d);
16         System.out.println(e == f);
17         System.out.println(c == (a+b));
18         System.out.println(c.equals(a+b));
19         //此時進行了自動的拆箱
20         System.out.println(g == (a+b));
21         System.out.println(g.equals(a+b));
22     }

答案是:

T

T

F

T

T

T

F


這樣的答案是不是出乎很多人的意料呢?我們一一來分析。

1. 首先我們明確一下"=="和equals方法的作用。

  "==":如果是基本數據類型,則直接對值進行比較,如果是引用數據類型,則是對他們的地址進行比較(但是只能比較相同類型的對象,或者比較父類對象和子類對象。類型不同的兩個對象不能使用==)

  equals方法繼承自Object類,在具體實現時可以覆蓋父類中的實現。看一下Object中qeuals的源碼發現,它的實現也是對對象的地址進行比較,此時它和"=="的作用相同。而JDK類中有一些類覆蓋了Object類的equals()方法,比較規則為:如果兩個對象的類型一致,並且內容一致,則返回true,這些類有:
java.io.file,java.util.Date,java.lang.string,包裝類(Integer,Double等)。

2. Java的包裝類實現細節。觀察源碼會發現Integer包裝類中定義了一個私有的靜態內部類如下:

 1 private static class IntegerCache {
 2     static final int low = -128;
 3     static final int high;
 4     static final Integer cache[];
 5 
 6     static {
 7         // high value may be configured by property
 8         int h = 127;
 9         String integerCacheHighPropValue =
10             sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
11         if (integerCacheHighPropValue != null) {
12             try {
13                 int i = parseInt(integerCacheHighPropValue);
14                 i = Math.max(i, 127);
15                 // Maximum array size is Integer.MAX_VALUE
16                 h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
17             } catch( NumberFormatException nfe) {
18                 // If the property cannot be parsed into an int, ignore it.
19             }
20         }
21         high = h;
22 
23         cache = new Integer[(high - low) + 1];
24         int j = low;
25         for(int k = 0; k < cache.length; k++)
26             cache[k] = new Integer(j++);
27 
28         // range [-128, 127] must be interned (JLS7 5.1.7)
29         assert IntegerCache.high >= 127;
30     }
31 
32     private IntegerCache() {}
33 }

而Integer的自動裝箱代碼:

1 public static Integer valueOf(int i) {
2     if (i >= IntegerCache.low && i <= IntegerCache.high)
3         return IntegerCache.cache[i + (-IntegerCache.low)];
4     return new Integer(i);
5 }

通過觀察上面的代碼我們可以發現,Integer使用一個內部靜態類中的一個靜態數組保存了-128-127范圍內的數據,靜態數組在類加載以后是存在方法區的,並不是什么常量池。在自動裝箱的時候,首先判斷要裝箱的數字的范圍,如果在-128-127的范圍則直接返回緩存中已有的對象,否則new一個新的對象。其他的包裝類也有類似的實現方式,可以通過源碼觀察一下。

3. "=="在遇到非算術運算符的情況下不會自動拆箱,以及他們的equals方法不處理數據類型轉換的關系。

因此,對於 System.out.println(c == d); 他們指向同一個對象,返回True。

對於 System.out.println(e == f); 他們的值大於127,即使值相同,但是對應不同的內存地址,返回false。

對於 System.out.println(c == (a+b)); 自動拆箱后他們的值是相等的,返回True。

對於 System.out.println(c.equals(a+b)); 他們的值相同,而且類型相同,返回true。

對於 System.out.println(g == (a+b)); 自動拆箱后他們的值相等,返回True。

對於 System.out.println(g.equals(a+b)); 他們的值相同但是類型不同,返回false。

4. 總結

對於不懂的地方,最好是通過閱讀源碼的方式來解決。這樣才能真正明白內部的一些實現方式。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM