標籤:lin body 取反 order show 位元組 浮點數 lsp jvm
以下是本文的目錄大綱:
一.什麼是裝箱?什麼是拆箱?
簡單一點說,裝箱就是 自動將基礎資料型別 (Elementary Data Type)轉換為封裝器類型;拆箱就是 自動將封裝器類型轉換為基礎資料型別 (Elementary Data Type)。
二.裝箱和拆箱是如何?的
1:反編譯class檔案:javap -c 類名
2:裝箱過程是通過調用封裝器(Integer)的valueOf方法實現的,而拆箱過程是通過調用封裝器的 xxxValue方法實現的。(xxx代表對應的基礎資料型別 (Elementary Data Type))。
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為每種基礎資料型別 (Elementary Data Type)都提供了對應的封裝器類型,至於為什麼會為每種基礎資料型別 (Elementary Data Type)提供封裝器類型在此不進行闡述,有興趣的朋友可以查閱相關資料。在Java SE5之前,如果要產生一個數值為10的Integer對象,必須這樣進行:
| 1 |
Integer i = new Integer(10); |
而在從Java SE5開始就提供了自動裝箱的特性,如果要產生一個數值為10的Integer對象,只需要這樣就可以了:
這個過程中會自動根據數值建立對應的 Integer對象,這就是裝箱。
那什麼是拆箱呢?顧名思義,跟裝箱對應,就是自動將封裝器類型轉換為基礎資料型別 (Elementary Data Type):
| 12 |
Integer i = 10; //裝箱int n = i; //拆箱 |
簡單一點說,裝箱就是 自動將基礎資料型別 (Elementary Data Type)轉換為封裝器類型;拆箱就是 自動將封裝器類型轉換為基礎資料型別 (Elementary Data Type)。
下表是基礎資料型別 (Elementary Data Type)對應的封裝器類型:
| int(4位元組) |
Integer |
| byte(1位元組) |
Byte |
| short(2位元組) |
Short |
| long(8位元組) |
Long |
| float(4位元組) |
Float |
| double(8位元組) |
Double |
| char(2位元組) |
Character |
| boolean(未定) |
Boolean |
二.裝箱和拆箱是如何?的
上一小節瞭解裝箱的基本概念之後,這一小節來瞭解一下裝箱和拆箱是如何?的。
我們就以Interger類為例,下面看一段代碼:
| 1234567 |
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代表對應的基礎資料型別 (Elementary Data Type))。
三.面試中相關的問題
雖然大多數人對裝箱和拆箱的概念都清楚,但是在面試和筆試中遇到了與裝箱和拆箱的問題卻不一定會答得上來。下面列舉一些常見的與裝箱/拆箱有關的面試題。
1;先看一個問題,對於下面定義的四個變數進行==比較:
| 123456 |
Integer a=1;Integer b=1;Integer c=200;Integer d=200;System.out.println(a==b);//trueSystem.out.println(c==d);//false |
我們都知道==在JAVA裡面是比較對象引用的,如果兩個對象引用指向堆中的同一塊記憶體就返回true,否則返回false。根據自動裝箱規則我們知道Integer a = 1 <==> Integer a = Integer.valueOf(1);,但是在valueOf方法上,查看源碼:
| 1234567 |
public static Integer valueOf(int i) { final int offset = 128; if (i >= -128 && i <= 127) { // must cache return IntegerCache.cache[i + offset]; } return new Integer(i);} |
Integer.valueOf()中有個內部類IntegerCache(類似於一個常量數組,也叫對象池),它維護了一個Integer數組cache,長度為(128+127+1)=256。Integer類中還有一個Static Block(靜態塊)
| 1234 |
static { for(int i = 0; i < cache.length; i++) cache[i] = new Integer(i - 128);} |
從這個靜態塊可以看出,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.下面這段代碼的輸出結果是什嗎?
| 123456789101112 |
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.下面程式的輸出結果是什嗎?
| 123456789101112131415161718192021 |
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方法)。
如果對上面的具體執行過程有疑問,可以嘗試擷取反編譯的位元組碼內容進行查看。
深入剖析Java中的裝箱和拆箱