上篇文章我們簡單介紹了包裝的相關基本概念,並簡單分析了 Integer 類中的幾個核心的方法原始碼,但是有關自動拆裝箱的概念限於篇幅並沒能完成介紹,本篇還將分析幾種常見的包裝類面試題,深入理解一下我們的包裝類設計。
自動拆裝箱
所謂「拆箱」就是指,包裝型別轉換為基本型別的過程,而所謂的「裝箱」則是基本型別到包裝型別的過程。例如:
public static void main(String[] args){
int age = 21;
Integer integer = new Integer(age); //裝箱
int num = integer.intValue(); //拆箱
}
複製程式碼
自從 jdk1.5 以後,引入了自動拆裝箱的概念,上述程式碼可以簡化成如下程式碼:
public static void main(String[] args){
int age = 21;
Integer integer = age; //自動裝箱
int num = integer; //自動拆箱
}
複製程式碼
是不是感覺簡便了很多,但是實際上在 JVM 層面是沒有變化的,這都是編譯器做的「假象」。
只是編譯器允許你這樣書寫程式碼了,其實編譯成位元組碼指令的時候,編譯器還是會呼叫相應的拆裝箱方法的。
可以看到,拆裝箱是需要方法呼叫的,也就是需要棧幀的入棧出棧的,直白點說,就是耗資源,所以我們的程式中應當儘量避免大量的「拆裝箱」操作。
面試題
面試題一:
public static void main(String[] args){
Integer i1 = 100;
Integer i2 = 100;
Integer i3 = 200;
Integer i4 = 200;
System.out.println(i1==i2);
System.out.println(i3==i4);
}
複製程式碼
如果之前沒了解過 Integer 內部原始碼的人想必會對輸出的結果「百思不得其解」。
輸出結果為:
true
false
複製程式碼
如果你認真看完了我的兩篇文章,這個問題應該不難解釋。
直接將整型數值賦值給 Integer 例項將發生裝箱操作,也就是呼叫 valueOf 方法,而這個方法我們分析過,會首先檢查一下 100 是否在快取池是否快取了,當然 IntegerCache 會預設快取 [-128,127] 之間的 Integer 例項,所以這裡會直接從快取池中取出引用賦值給變數 i1 。
同理 i2 也會從快取池中取引用,並且兩者的引用的是同一個堆物件,所以才會輸出 「true」。
而第二個輸出「false」也是很好理解的,因為 200 不再快取池快取的範圍內,所以每次呼叫 valueOf 方法都會新建一個不同的 Integer 例項。
面試題二:
public static void main(String[] args){
Double i1 = 100.0;
Double i2 = 100.0;
Double i3 = 200.0;
Double i4 = 200.0;
System.out.println(i1==i2);
System.out.println(i3==i4);
}
複製程式碼
很多人會認為這段程式碼的輸出結果會和上題一樣,但是其實不然:
false
false
複製程式碼
那是因為 Double 這個包裝類並沒有快取池的概念,也就是說它會為每一個 double 型數值包裝一個新的 Double 例項。正如它的 valueOf 方法:
public static Double valueOf(double d) {
return new Double(d);
}
複製程式碼
這裡可能有人會疑問了,為什麼 Integer 用快取池提升效率,而 Double 卻棄之不用呢?
其實也很簡單,你會發現 IntegerCache 是用 Integer 陣列快取了某個區間的所有數值對應的 Integer 例項,那麼請問給定一個區間 [-128.0,127.0],你能確定之中有多少個 double 數值嗎?
因為任意一個區間,哪怕再小的區間都對應的無窮盡的 double 小數,所以無法進行快取。
同樣的問題也適用於 Float。
面試題三:
public static void main(String[] args){
Boolean i1 = false;
Boolean i2 = false;
Boolean i3 = true;
Boolean i4 = true;
System.out.println(i1==i2);
System.out.println(i3==i4);
}
複製程式碼
輸出結果:
true
true
複製程式碼
Boolean 的 valueOf 方法是這樣的:
public static final Boolean TRUE = new Boolean(true);
public static final Boolean FALSE = new Boolean(false);
public static Boolean valueOf(boolean b) {
return (b ? TRUE : FALSE);
}
複製程式碼
顯然,結果相信不再需要多做解釋了。
最後需要提一下的是,八種包裝類中有以下五種是支援「快取池」的。
- Integer:對應的快取池型別為 IntegerCache
- Byte:對應的快取池型別為 ByteCache
- Short:對應的額快取池型別為 ShortCache
- Long:對應的額快取池型別為 LongCache
- Character:對應的快取池型別為 CharacterCache
其實 Boolean 的實現比較特殊,因為它只有兩種取值可能,其實也能夠算作支援快取功能的。
文章中的所有程式碼、圖片、檔案都雲端儲存在我的 GitHub 上:
(https://github.com/SingleYam/overview_java)
歡迎關注微信公眾號:撲在程式碼上的高爾基,所有文章都將同步在公眾號上。