深刻剖析Java中的裝箱和拆箱

原文出處: 海子面試

自動裝箱和拆箱問題是Java中一個老生常談的問題了,今天咱們就來一些看一下裝箱和拆箱中的若干問題。本文先講述裝箱和拆箱最基本的東西,再來看一下面試筆試中常常遇到的與裝箱、拆箱相關的問題。編程

如下是本文的目錄大綱:架構

一.什麼是裝箱?什麼是拆箱?網站

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

三.面試中相關的問題spa

如有不正之處,請諒解和批評指正,不勝感激。code

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

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

 

1
Integer i =  new  Integer( 10 );

而在從Java SE5開始就提供了自動裝箱的特性,若是要生成一個數值爲10的Integer對象,只須要這樣就能夠了:對象

 

1
Integer i =  10 ;

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

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

 

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文件以後獲得以下內容:

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

其餘的也相似,好比Double、Character,不相信的朋友能夠本身手動嘗試一下。

所以能夠用一句話總結裝箱和拆箱的實現過程:

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

三.面試中相關的問題

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

1.下面這段代碼的輸出結果是什麼?

 

1
2
3
4
5
6
7
8
9
10
11
12
public  class  Main {
     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);
     }
}

也許有些朋友會說都會輸出false,或者也有朋友會說都會輸出true。可是事實上輸出結果是:

true
false

爲何會出現這樣的結果?輸出結果代表i1和i2指向的是同一個對象,而i3和i4指向的是不一樣的對象。此時只需一看源碼便知究竟,下面這段代碼是Integer的valueOf方法的具體實現:

1
2
3
4
5
6
public  static  Integer valueOf( int  i) {
         if (i >= - 128  && i <= IntegerCache.high)
             return  IntegerCache.cache[i +  128 ];
         else
             return  new  Integer(i);
     }

而其中IntegerCache類的實現爲:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
private  static  class  IntegerCache {
         static  final  int  high;
         static  final  Integer cache[];
 
         static  {
             final  int  low = - 128 ;
 
             // high value may be configured by property
             int  h =  127 ;
             if  (integerCacheHighPropValue !=  null ) {
                 // Use Long.decode here to avoid invoking methods that
                 // require Integer's autoboxing cache to be initialized
                 int  i = Long.decode(integerCacheHighPropValue).intValue();
                 i = Math.max(i,  127 );
                 // Maximum array size is Integer.MAX_VALUE
                 h = Math.min(i, Integer.MAX_VALUE - -low);
             }
             high = h;
 
             cache =  new  Integer[(high - low) +  1 ];
             int  j = low;
             for ( int  k =  0 ; k < cache.length; k++)
                 cache[k] =  new  Integer(j++);
         }
 
         private  IntegerCache() {}
     }

從這2段代碼能夠看出,在經過valueOf方法建立Integer對象的時候,若是數值在[-128,127]之間,便返回指向IntegerCache.cache中已經存在的對象的引用;不然建立一個新的Integer對象。

上面的代碼中i1和i2的數值爲100,所以會直接從cache中取已經存在的對象,因此i1和i2指向的是同一個對象,而i3和i4則是分別指向不一樣的對象。

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、Short、Byte、Character、Long這幾個類的valueOf方法的實現是相似的。

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

3.下面這段代碼輸出結果是什麼:

 

1
2
3
4
5
6
7
8
9
10
11
12
public  class  Main {
     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類的源碼也會一目瞭然。下面是Boolean的valueOf方法的具體實現:

1
2
3
public  static  Boolean valueOf( boolean  b) {
         return  (b ? TRUE : FALSE);
     }

而其中的 TRUE 和FALSE又是什麼呢?在Boolean中定義了2個靜態成員屬性:

1
2
3
4
5
6
7
public  static  final  Boolean TRUE =  new  Boolean( true );
 
     /**
      * The <code>Boolean</code> object corresponding to the primitive 
      * value <code>false</code>.
      */
     public  static  final  Boolean FALSE =  new  Boolean( false );

至此,你們應該明白了爲什麼上面輸出的結果都是true了。

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方法)。

若是對上面的具體執行過程有疑問,能夠嘗試獲取反編譯的字節碼內容進行查看。

若是有哪位朋友有補充的內容,歡迎下方留言,不勝感激。

問啊-定製化IT教育平臺,牛人一對一服務,有問必答,開發編程社交頭條 官方網站:www.wenaaa.com

QQ羣290551701 彙集不少互聯網精英,技術總監,架構師,項目經理!開源技術研究,歡迎業內人士,大牛及新手有志於從事IT行業人員進入!

相關文章
相關標籤/搜索