Java是個高階語言,其實是可以忽略自動裝箱、拆箱內幕,不過對初學者來說,為了能清楚區別基本型態與類別型態之不同,瞭解自動裝箱、拆箱內幕其實是有所幫助。
自動裝箱與拆箱的功能事實上是編譯器蜜糖(Compiler sugar),也就是編譯器讓你撰寫程式時吃點甜頭,編譯時期依所撰寫的語法,決定是否進行裝箱或拆箱動作。例如:
Integer i = 100;
在Oracle的JDK上,編譯器會自動將程式碼展開為:
Integer i = Integer.valueOf(100);
Java的位元碼格式也是公開標準,有位元碼檔案,就可以嘗試使用反組譯程式轉譯為Java語法。這邊使用的反組譯程式是 JAD。
使用Integer.valueOf()
也是為基本型態建立包裹器的方式之一。瞭解編譯器會如何裝箱與拆箱是必要的,例如下面的程式是可以通過編譯的:
Integer i = null;
int j = i;
但是在執行時期會有錯誤,因為編譯器會將之展開為:
Integer integer = null;
int i = integer.intValue();
在Java程式碼中,null
代表一個特殊物件,任何類別宣告的參考名稱都可以參考至null
,表示該名稱沒有參考至任何物件實體,這相當於有個名牌沒有任何人佩戴。在上例中,由於i
並沒有參考至任何物件,所以就不可能操作intValue()
方法,就相當於有個名牌沒有人佩戴,你卻要求戴名牌的人舉手,這是一種錯誤,在Java中會出現NullPointerException
的錯誤訊息。
編譯器蜜糖通常提供了方便性,但也因此隱藏了一些細節,所以別只顧著吃糖而忽略了該知道的觀念。來看看,如果你如下撰寫,結果會是如何?
Integer i1 = 100;
Integer i2 = 100;
if (i1 == i2) {
System.out.println("i1 == i2");
}
else {
System.out.println("i1 != i2");
}
如果只看Integer i1 = 100
,就好像在看int i1 = 100
,直接使用==
進行比較,有的人會理所當然回答顯示i1 == i2,那麼底下這個呢?
Integer i1 = 200;
Integer i2 = 200;
if (i1 == i2) {
System.out.println("i1 == i2");
}
else {
System.out.println("i1 != i2");
}
注意!程式碼只不過將100改為200,但執行結果會顯示i1 != i2,這是為何?先前提過,自動裝箱是編譯器蜜糖,以上例來說,實際上會使用Integer.valueOf()
來建立Integer
實例,所以你要知道Integer.valueOf()
到底如何建立Integer
實例,察查JDK資料夾src.zip中的java/lang資料夾中的Integer.java,你會看到valueOf()
的實作內容:
public static Integer valueOf(int i) {
assert IntegerCache.high >= 127;
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
這段程式碼簡單來說,就是如果傳入的int
在IntegerCache.low
與IntegerCache.high
之間,那就嘗試看看先前快取(Cache)中有沒有包裹過相同的值,如果有就直接傳回,否則就使用new
建構新的Integer
實例。IntegerCache.low
預設值是-128,IntegerCache.high
預設值是127。
所以如果是這個程式碼:
Integer i1 = 100;
Integer i2 = 100;
第一行程式碼由於100在-128到127間,會從快取中傳回Integer
實例,第二行程式碼執行時,要包裹的同樣是100,也是從快取中傳回同一Integer
實例,所以i1
與i2
會參考到同一個Integer
實例,使用==
比較就會是true
。
如果是這個程式碼:
Integer i1 = 200;
Integer i2 = 200;
第一行程式碼由於100不在-128到127間,所以直接建立Integer
實例,第二行程式碼執行時,也是直接建立新的Integer
實例,所以i1
與i2
不會參考到同一個Integer
實例,使用==
比較就會是false
。
IntegerCache.low
預設值是-128,執行時期無法更改,IntegerCache.high
預設值是127,可以於啟動JVM時,使用系統屬性java.lang.Integer.IntegerCache.high來指定。例如:
如上指定之後,Integer
就會針對-128到300範圍中建立的包裹器進行快取,而針對先前i1
與i2
包裹200時,使用==
比較的結果,就又顯示i1 == i2了。
所以結論就是還是一樣,別使用==
或!=
來比較兩個物件實質內容值是否相同(因為==
與!=
是比較物件參考),而要使用equals()
。例如以下的程式碼:
Integer i1 = 200;
Integer i2 = 200;
if (i1.equals(i2)) {
System.out.println("i1 == i2");
}
else {
System.out.println("i1 != i2");
}
無論實際上i1
與i2
包裹的值座落在哪個範圍,只要i1
與i2
包裹的值相同,equals()
比較的結果就會是true
。