深入剖析Java中的裝箱和拆箱(緩存池技術)


以下是本文的目錄大綱:

一.什么是裝箱?什么是拆箱?

  簡單一點說,裝箱就是  自動將基本數據類型轉換為包裝器類型;拆箱就是  自動將包裝器類型轉換為基本數據類型。

二.裝箱和拆箱是如何實現的

  1:反編譯class文件:javap -c 類名

  2:裝箱過程是通過調用包裝器(Integer)的valueOf方法實現的,而拆箱過程是通過調用包裝器的 xxxValue方法實現的。(xxx代表對應的基本數據類型)。

  3:注意,Integer、Short、Byte、Character、Long這幾個類的valueOf方法的實現是類似的。

    Double、Float的valueOf方法的實現是類似的。

三.面試中相關的問題

  1:Integer a=1時發生裝箱過程中使用valueOf方法,Integer.valueOf()中有個靜態內部類IntegerCache,里面有個常量cache[],也就是Integer常量池(其實就是緩存池技術,利用空間換時間的策略,也叫對象池),在常量池(對象池)中Integer已經默認創建了數值【-128-127】的Integer緩存數據。所以使用Integer a=1時,JVM會直接在該在對象池找到該值的引用。也就是說這種方式聲明一個Integer對象時,JVM首先會在Integer對象的緩存池中查找有木有值為1的對象,如果有直接返回該對象的引用;如果沒有,則使用New Integer(在jvm的堆中new一個)創建一個對象,並返回該對象的引用地址。

注意:最大值 127 可以通過 JVM 的啟動參數 -XX:AutoBoxCacheMax=size 修改

  2:int與Integer比較時,會自動裝箱

  

  3:談談Integer i = new Integer(xxx)和Integer i =xxx;這兩種方式的區別。

    當然,這個題目屬於比較寬泛類型的。但是要點一定要答上,我總結一下主要有以下這兩點區別:

    1)第一種方式不會觸發自動裝箱的過程;而第二種方式會觸發;

    2)在執行效率和資源占用上的區別。第二種方式的執行效率和資源占用在一般性情況下要優於第一種情況,當值不在(-128-127)之間時(注意這並不是絕對的)。

 

 

一.什么是裝箱?什么是拆箱?

  在前面的文章中提到,Java為每種基本數據類型都提供了對應的包裝器類型,至於為什么會為每種基本數據類型提供包裝器類型在此不進行闡述,有興趣的朋友可以查閱相關資料。在Java SE5之前,如果要生成一個數值為10的Integer對象,必須這樣進行:

1
Integer i =  new  Integer( 10 );

  而在從Java SE5開始就提供了自動裝箱的特性,如果要生成一個數值為10的Integer對象,只需要這樣就可以了:

1
Integer i =  10 ;

  這個過程中會自動根據數值創建對應的 Integer對象,這就是裝箱。

  那什么是拆箱呢?顧名思義,跟裝箱對應,就是自動將包裝器類型轉換為基本數據類型:

1
2
Integer i =  10 ;   //裝箱
int  n = i;    //拆箱

  簡單一點說,裝箱就是  自動將基本數據類型轉換為包裝器類型;拆箱就是  自動將包裝器類型轉換為基本數據類型。

  下表是基本數據類型對應的包裝器類型:

int(4字節) Integer
byte(1字節) Byte
short(2字節) Short
long(8字節) Long
float(4字節) Float
double(8字節) Double
char(2字節) Character
boolean(未定) Boolean

二.裝箱和拆箱是如何實現的

  上一小節了解裝箱的基本概念之后,這一小節來了解一下裝箱和拆箱是如何實現的。

  我們就以Interger類為例,下面看一段代碼:

1
2
3
4
5
6
7
public  class  Main {
     public  static  void  main(String[] args) {
         
         Integer i =  10 ;
         int  n = i;
     }
}

  反編譯class文件之后得到如下內容:javap -c 類名

  

  從反編譯得到的字節碼內容可以看出,在裝箱的時候自動調用的是Integer的valueOf(int)方法。而在拆箱的時候自動調用的是Integer的intValue方法。

  其他的也類似,比如Double、Character,不相信的朋友可以自己手動嘗試一下。

  因此可以用一句話總結裝箱和拆箱的實現過程:

  裝箱過程是通過調用包裝器的valueOf方法實現的,而拆箱過程是通過調用包裝器的 xxxValue方法實現的。(xxx代表對應的基本數據類型)。

三.面試中相關的問題

  雖然大多數人對裝箱和拆箱的概念都清楚,但是在面試和筆試中遇到了與裝箱和拆箱的問題卻不一定會答得上來。下面列舉一些常見的與裝箱/拆箱有關的面試題。

1;先看一個問題,對於下面定義的四個變量進行==比較:

我們都知道==在JAVA里面是比較對象引用的,如果兩個對象引用指向堆中的同一塊內存就返回true,否則返回false。根據自動裝箱規則我們知道Integer a = 1 <==> Integer a = Integer.valueOf(1);,但是在valueOf方法上,查看源碼:

Integer.valueOf()中有個內部類IntegerCache(類似於一個常量數組,也叫對象池),它維護了一個Integer數組cache,長度為(128+127+1)=256。Integer類中還有一個Static Block(靜態塊)

從這個靜態塊可以看出,Integer已經默認創建了數值【-128-127】的Integer緩存數據。所以使用Integer a=1時,JVM會直接在該在對象池找到該值的引用。也就是說這種方式聲明一個Integer對象時,JVM首先會在Integer對象的緩存池中查找有木有值為1的對象,如果有直接返回該對象的引用;如果沒有,則使用New Integer創建一個對象,並返回該對象的引用地址。因為Java中==比較的是兩個對象是否是同一個引用(即比較內存地址),a和b都是引用的同一個對象,所以a==b結果為true;c和d已經超出了緩存的范圍,所以重新生成了Integer對象,所以c==d結果為false。

2.下面這段代碼的輸出結果是什么?

1
2
3
4
5
6
7
8
9
10
11
12
public  class  Main {
     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類的valueOf的實現。

  在這里只解釋一下為什么Double類的valueOf方法會采用與Integer類的valueOf方法不同的實現。很簡單:在某個范圍內的整型數值的個數是有限的,而浮點數卻不是。

下面我們進行一個歸類: 
Integer派別:Integer、Short、Byte、Character、Long這幾個類的valueOf方法的實現是類似的。 
Double派別:Double、Float的valueOf方法的實現是類似的。每次都返回不同的對象。

下面對Integer派別進行一個總結,如下圖: 
這里寫圖片描述

4.談談Integer i = new Integer(xxx)和Integer i =xxx;這兩種方式的區別。

  當然,這個題目屬於比較寬泛類型的。但是要點一定要答上,我總結一下主要有以下這兩點區別:

  1)第一種方式不會觸發自動裝箱的過程;而第二種方式會觸發;

  2)在執行效率和資源占用上的區別。第二種方式的執行效率和資源占用在一般性情況下要優於第一種情況(注意這並不是絕對的)。

5.下面程序的輸出結果是什么?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public  class  Main {
     public  static  void  main(String[] args) {
         
         Integer a =  1 ;
         Integer b =  2 ;
         Integer c =  3 ;
         Integer d =  3 ;
         Integer e =  321 ;
         Integer f =  321 ;
         Long g = 3L;
         Long h = 2L;
         
         System.out.println(c==d);
         System.out.println(e==f);
         System.out.println(c==(a+b));
         System.out.println(c.equals(a+b));
         System.out.println(g==(a+b));
         System.out.println(g.equals(a+b));
         System.out.println(g.equals(a+h));
     }
}

  先別看輸出結果,讀者自己想一下這段代碼的輸出結果是什么。這里面需要注意的是:當 "=="運算符的兩個操作數都是 包裝器類型的引用,則是比較指向的是否是同一個對象,而如果其中有一個操作數是表達式(即包含算術運算)則比較的是數值(即會觸發自動拆箱的過程)。另外,對於包裝器類型,equals方法並不會進行類型轉換。明白了這2點之后,上面的輸出結果便一目了然:

 

true
false
true
true
true
false
true

  第一個和第二個輸出結果沒有什么疑問。第三句由於  a+b包含了算術運算,因此會觸發自動拆箱過程(會調用intValue方法),因此它們比較的是數值是否相等。而對於c.equals(a+b)會先觸發自動拆箱過程,再觸發自動裝箱過程,也就是說a+b,會先各自調用intValue方法,得到了加法運算后的數值之后,便調用Integer.valueOf方法,再進行equals比較。同理對於后面的也是這樣,不過要注意倒數第二個和最后一個輸出的結果(如果數值是int類型的,裝箱過程調用的是Integer.valueOf;如果是long類型的,裝箱調用的Long.valueOf方法)。

如果對上面的具體執行過程有疑問,可以嘗試獲取反編譯的字節碼內容進行查看。


免責聲明!

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



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