天天看點

底層原理_自動裝箱與拆箱底層原理

1.自動裝箱與拆箱

    Java中的資料類型分為兩大類,基本資料類型與引用資料類型。Java中共提供了八種基本資料類型,同時提供了這八種基本資料類型對應的引用資料類型。

    自動裝箱:基本資料類型的資料自動轉化為對應的引用資料類型的資料

Integer integerVal = 0;
           

    自動拆箱:引用資料類型的資料自動轉化為對應的基本資料類型的資料

int intVal = new Integer(0);
           

    基本資料類型變量中存儲的就是資料值,但是引用資料類型的變量實際上指向的是此引用對象的位址值,那麼按照這種思想,指派的操作應該是将對應的資料值作為引用位址或者是引用類型對象的位址值指派給基本資料類型才對,為什麼自動裝箱與拆箱的操作就能将其轉化成對應的資料呢?

    下面就來說一下這種裝箱與拆箱的機制到底底層是如何實作的,如何能實作基本資料類型與對應引用資料類型間的自動轉化。

2.底層原理

    以下剖析原理以int與Integer類型為例

    自動裝箱的底層原理:自動裝箱實際上調用的是Integer中的靜态方法valueOf(),将基本資料類型的int數值包裝成了一個Integer對象

Integer integerVal = 0;//等效于Integer integerVal = Integer.valueOf(0);
           

    自動拆箱的底層原理:自動拆箱的底層實際上調用的是Integer對象的intValue(),得到對象内的int變量的數值,然後給指派給變量

int intVal = new Integer(0);//等效于int intVal = new Integer(0).intValue();
           

    說到這裡,可能很多人知道自動裝箱與自動拆箱是與這兩個方法有關,但是空說無憑,下面從位元組碼層面來看一下,為什麼大家都說自動裝箱與自動拆箱與這兩個方法有關,在哪裡能證明這兩個方法就是自動裝箱與拆箱的本質。

3.位元組碼原理探究

(1)位元組碼檔案

    剛開始學Java的時候,大家應該都做過一件事件,那就是編寫.java源檔案,然後使用JDK中提供的javac.exe編譯器将.java源檔案編譯成.class檔案,class字尾的檔案也就是Java中的位元組碼檔案,然後使用java.exe工具來執行位元組碼檔案。

    也就是說JVM中執行的實際上是.class字尾的位元組碼檔案,以下就來解析位元組碼内容,檢視裝箱與拆箱的本質(不懂JVM的朋友也沒有關系,可以一起看一下從位元組碼層面來看,自動裝箱與拆箱到底位元組碼中是如何表述的)

(2)自動裝箱

    由于位元組碼檔案是以二進制來存儲資訊的,是以直接打開是看不到内容的,JDK中提供的javap.exe工具來将位元組碼檔案解析成文本

//源碼public class MainClass {    public static void main(String[] args) {        Integer integerVal = 0;    }}
           
//javac編譯得到位元組碼javac MainClass.java//javap解析位元組碼内容(-v -p參數有興趣的可以自行研究一下)javap -v -p MainClass.clas
           

    使用javap工具解析後得到解析的内容:

底層原理_自動裝箱與拆箱底層原理

    這裡隻關注public static void main()方法内的内容,Code中内部的一行行代碼就稱之為位元組碼指令

    可以看到在位元組碼層面調用了Integer.valueOf()方法。

    看到這裡就會發現,其實編譯過後,從位元組碼層面所看的行為與源碼的層面還是存在着一定的差異的,這也就是Java為開發人員屏蔽了底層細節,讓開發人員更加關注上層的文法,提高開發效率

    從這裡也就可以看出,自動裝箱的機制确實是調用了Integer.valueOf()方法,将基本資料類型轉化為對應的引用資料類型

(3)自動拆箱

    自動拆箱的驗證過程與自動裝箱的過程一樣,需要使用javap.exe工具将位元組碼内容解析出來

//源碼public class MainClass {    public static void main(String[] args) {        Integer integer = new Integer(0);        int intVal = integer;    }}
           
//javac編譯得到位元組碼javac MainClass.java//javap解析位元組碼内容(-v -p參數有興趣的可以自行研究一下)javap -v -p MainClass.clas
           

    使用javap工具解析後得到解析的内容:

底層原理_自動裝箱與拆箱底層原理

    前面的位元組碼可以不用看,是建立Integer部分,在标記10的位置,調用了Integer對象的intValue()方法,這也就是自動拆箱的原理,從基本資料類型自動轉換到對應的引用資料類型

    以上所說明的是int與Integer類型的自動裝箱與拆箱原理,其他的幾種基本資料類型也是一樣的,裝箱與拆箱分别調用的都是XXX.valueOf()與xxxValue()方法,這裡就不一一列舉的,具體的檢視細節同上

    知道了自動裝箱與拆箱的原理,大家應該也就能知道下面列舉的代碼的執行結果了:

Integer integerVal = null;int val = integerVal;
           

4.資料緩存池

(1)面試題

    既然聊了基本資料類型的自動裝箱與拆箱這一塊,就順帶來聊一聊什麼是資料緩存池

    以下是以前看到過的一則面試題:

Integer val1 = 127;Integer val2 = 127;Integer val3 = 128;Integer val4 = 128;System.out.println(val1 == val2);System.out.println(val3 == val4);
           

    剛剛已經聊過了自動裝箱問題,也就知道這裡實際上是發生了四次裝箱,得到了四個Integer對象。

    既然Integer是引用資料類型,那麼==号比較的就是這兩個引用對象的位址是否相等,但是這裡并沒有任何的引用之間互相指派,按理說應該是四個不相同的對象,但是結果卻出人意料

底層原理_自動裝箱與拆箱底層原理

    根據結果可以推斷出val1與val2實際上引用位址是一樣的,但是val3與val4又是不一樣的對象

    這裡的Integer對象都是由Integer的靜态方法valueOf()來建立的,是以就需要看一看valueOf()方法裡面到底做了什麼,是不是單純地建立對象那麼簡單。

(2)資料緩存池   

//Integer.valueOf()源碼public static Integer valueOf(int i) {    if (i >= IntegerCache.low && i <= IntegerCache.high)        return IntegerCache.cache[i + (-IntegerCache.low)];    return new Integer(i);}
           

    可以看到,在valueOf()方法内并不是單純地建立對象那麼簡單,而是先判斷需要轉換的資料是否在low~high範圍内,如果在範圍内,直接從一個cache數組中取出對應槽位中的Integer對象;如果在此範圍内的話,就直接new一個Integer對象傳回。

    下面再來看一下IntegerCache的cache數組到底裝的是什麼:

private static class IntegerCache {    static final int low = -128;    static final int high;    static final Integer cache[];    static {        int h = 127;        //......        high = h;        cache = new Integer[(high - low) + 1];        int j = low;        for(int k = 0; k < cache.length; k++)            cache[k] = new Integer(j++);        //......    }    //......}
           

    IntegerCache是Integer中的一個靜态内部類,上述省略了部分代碼,隻留下了關鍵性的代碼。

    可以看到在此類的靜态代碼塊中會建立cache數組,長度也就是127-(-128)=255,後續接着就是一個循環,也就是初始化輸出,資料為-128~127的Integer對象。

    由于static代碼塊是在程式啟動的時候就會運作的,是以也就是說,在程式啟動的時候,Integer中的IntegerCache中的cache數組就被初始化為了-128~127之間,一共255個Integer對象。

    再回到valueOf()方法中來看,判斷數值是否為-128~127之間,如果是的話,就直接傳回cache數組中對應的Integer對象,如果在範圍内,才是new一個Integer對象并傳回。

    讀到這裡,也就能說明為什麼val1與val2是同一個對象,但是val3與val4并不是同一個對象了:因為val1與val2都是調用valueOf()方法,參數都是127,127屬于-128~127之間,是以是直接從IntegerCache.cache數組中取出建立好的Integer對象,并且都是從同一個槽位中取的,是以val1與val2是同一個對象;但是128超出了-128~127的範圍,是以不在範圍内而直接new了Integer對象,是以val3與val4都是new的不同的Integer對象,故不是同一個對象

    這裡的IntegerCache也就是所謂的資料緩存池,起到一定範圍内緩存對象的目的,避免項目中重複建立相同的Integer對象,浪費空間又損耗性能

其他資料資料類型的緩存池:

    除了Integer中存在資料緩存池外,其他的還有Byte、Character、Short、Long

    此外Boolean中的資料緩存池比較特殊,由于boolean隻有true與false,是以直接定義了兩個常量TRUE與FALSE,也就是說,所有使用到的Boolean對象都是TRUE與FALSE兩個對象。

5.總結

  • 自動裝箱指的是基本資料類型自動轉化為對應的引用資料類型
  • 自動拆箱指的是引用資料類型自動轉化為對應的基本資料類型
  • 從位元組碼層面發現,自動裝箱實際調用的是XXX.valueOf()方法将對應的資料包裝成對應的對象;自動拆箱是實際調用的是對象的xxxValue()方法傳回對象内的基本資料類型資料
  • 自動裝箱的時候,存在一個資料緩存池問題,資料在一定範圍内使用的是緩存池中在程式啟動的時候建立好的對象,超出範圍才是直接new的對象
  • 此外,除了基本資料類型使用“==”号判斷相等外,引用資料類型一定要使用equals()方法來判斷相等(除了特殊情況下需要比對是否為同一個對象的情況下)

    最後,極力推薦有時間的朋友可以去學一下JVM(推薦尚矽谷宋紅康老師講的JVM視訊),可能學習了JVM你看不到太大實質性地改變,但是對更多的底層原理有了一定的了解,碰到問題的時候也就不需要死記硬背了,從原理出發更好地解決問題

繼續閱讀