基本資料型別及其包裝類(二)

YangAM發表於2018-04-14

上篇文章我們簡單介紹了包裝的相關基本概念,並簡單分析了 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 層面是沒有變化的,這都是編譯器做的「假象」。

image

只是編譯器允許你這樣書寫程式碼了,其實編譯成位元組碼指令的時候,編譯器還是會呼叫相應的拆裝箱方法的。

可以看到,拆裝箱是需要方法呼叫的,也就是需要棧幀的入棧出棧的,直白點說,就是耗資源,所以我們的程式中應當儘量避免大量的「拆裝箱」操作。

面試題

面試題一:

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)

歡迎關注微信公眾號:撲在程式碼上的高爾基,所有文章都將同步在公眾號上。

image

相關文章