什么的拆裝箱
我第一次聽到這個概念是一臉懵逼的,而其實最常使用的地方就是我們熟悉的包裝類的使用中.比如將int的變量轉(zhuǎn)換成Integer對象对途,這個過程叫做裝箱党巾,反之將Integer對象轉(zhuǎn)換成int類型值,這個過程叫做拆箱。因為這里的裝箱和拆箱是自動進行的非人為轉(zhuǎn)換,所以就稱作為自動裝箱和拆箱拔恰。
需要明確的是自動拆裝箱是在JDK1.5以后引入的,對于之前版本的Java焊夸,需要格外注意格式的轉(zhuǎn)換仁连。
為何需要自動裝箱和拆箱?
方便
首先就是方便程序員的編碼阱穗,我們在編碼過程中饭冬,可以不需要考慮包裝類和基本類型之間的轉(zhuǎn)換操作,這一步由編譯器自動替我們完成揪阶,開發(fā)人員可以有更多的精力集中與具體的業(yè)務(wù)邏輯昌抠。否則的話,一個簡單的數(shù)字賦值給包裝類就得寫兩句代碼鲁僚,即:首先生成包裝類型對象炊苫,然后將對象轉(zhuǎn)換成基本數(shù)據(jù)類型。而這種操作是代碼中使用頻率很高的操作冰沙,導(dǎo)致代碼書寫量增多侨艾。
節(jié)約空間
我們在查閱對應(yīng)包裝類的源代碼時可以看到,大部分包裝類型的valueOf方法都會有緩存的操作拓挥,即:將某段范圍內(nèi)的數(shù)據(jù)緩存起來唠梨,創(chuàng)建時如果在這段范圍內(nèi),直接返回已經(jīng)緩存的這些對象侥啤,這樣保證在一定范圍內(nèi)的數(shù)據(jù)可以直接復(fù)用当叭,而不必要重新生成。
這么設(shè)計的目的因為:小數(shù)字的使用頻率很高盖灸,將小數(shù)字緩存起來蚁鳖,讓其僅有一個對象,可以起到節(jié)約存儲空間的作用赁炎。這里其實采用的是一種叫做享元模式的設(shè)計模式醉箕。可以去具體了解以下這種設(shè)計模式徙垫,這里就不再過多贅述讥裤。
實現(xiàn)原理
Java中是怎么實現(xiàn)這個自動裝箱和拆箱的過程的呢?這里需要借助與一些反編譯工具松邪,例如javap命令或者其他一些反編譯的工具,我這里使用的是idea的bytecode插件哨查,如果需要逗抑,可以到這里下載。在它的release中直接下載zip壓縮包就行,然后作為插件安裝在idea中就行邮府,安裝完成重啟idea后荧关,在需要反編譯的java代碼中右鍵,可以找到"Show Bytecode outline-dev"菜單選項褂傀,直接點擊就可以看到反編譯后的代碼忍啤。
裝箱
首先看下面兩句代碼:
Integer i = 20;
int j = 2;
在進行反編譯之后可以得到:
Integer i = Integer.valueOf((int)10);
int j = 2;
可以看到對于數(shù)值類型直接賦值給包裝類型,有一個自動裝箱的操作仙辟,而自動裝箱的操作就是利用了Integer中的valueOf方法同波,這就是前面在節(jié)約空間那部分提到的valueOf方法。Integer的valueOf方法中具有緩存的功能叠国,也就是說在數(shù)值為-128到127之間的數(shù)據(jù)未檩,都是被構(gòu)造成同一個對象,這就是上面提到的享元模式的設(shè)計思路:
public static Integer valueOf(int i) {
//IntegerCache.low = -128粟焊, IntegerCache.high = 127
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
這個概念在刷面試題的時候冤狡,都被強調(diào)爛了,基本常見的筆試題目就是比較幾個integer對象之間==操作:
Integer a = 100;
Integer b = 100;
Integer c = 128;
Integer d = 128;
System.out.println(a == b); //true
System.out.println(c == d); //false
注意:也可以使用new Integer(num)的方式創(chuàng)建Integer對象项棠,但是在JDK1.9之后悲雳,這個構(gòu)造方法被標(biāo)記為Deprecated,也就是過時了香追,所以以后盡量不要使用這種方式創(chuàng)建對象合瓢。它的注釋中建議使用valueOf進行構(gòu)建對象。利用構(gòu)造器構(gòu)造出來的對象不會經(jīng)過取緩存操作翅阵,所以對于new Integer(100)的操作歪玲,得到的Integer對象與a或b進行==比較時,得到的會是false掷匠。
其實其他七種包裝類型的valueOf方法大多都是這個享元設(shè)計模式的邏輯滥崩,但是有兩個除外:Float和Double。這個其實也很好理解:因為Integer這種類型的數(shù)據(jù)讹语,-128到127之間的數(shù)據(jù)是有限個钙皮,總共就256個數(shù)字,但是對于Float和Double這種類型顽决,它們之間的數(shù)據(jù)個數(shù)就無法計算了短条,所以它兩個就沒有采用這種緩存的方式。下面是其他包裝類型中的valueOf方法的源碼:
//Short
public static Short valueOf(short s) {
final int offset = 128;
int sAsInt = s;
if (sAsInt >= -128 && sAsInt <= 127) { // must cache
return ShortCache.cache[sAsInt + offset];
}
return new Short(s);
}
//Byte
public static Byte valueOf(byte b) {
final int offset = 128;
return ByteCache.cache[(int)b + offset];
}
//Character
public static Character valueOf(char c) {
if (c <= 127) { // must cache
return CharacterCache.cache[(int)c];
}
return new Character(c);
}
//Long
public static Long valueOf(long l) {
final int offset = 128;
if (l >= -128 && l <= 127) { // will cache
return LongCache.cache[(int)l + offset];
}
return new Long(l);
}
//Boolean
public static Boolean valueOf(boolean b) {
//public static final Boolean TRUE = new Boolean(true);
//public static final Boolean FALSE = new Boolean(false);
return (b ? TRUE : FALSE);
}
//Float
public static Float valueOf(float f) {
return new Float(f);
}
//Double
public static Double valueOf(double d) {
return new Double(d);
}
通過上面的代碼截圖可以看到才菠,對于Float和Double都是直接使用了構(gòu)造器直接構(gòu)造對應(yīng)包裝類型的對象茸时。對于Boolean類型,就是固定的兩個TRUE和FALSE兩個常量赋访,它們不會出現(xiàn)變化可都,這也屬于一種緩存缓待。
對于Byte類型,它是直接全部緩存了渠牲,這里使用了cache數(shù)組旋炒,它在Byte類中定義和初始化如下:
static final Byte cache[] = new Byte[-(-128) + 127 + 1];
static {
for(int i = 0; i < cache.length; i++)
cache[i] = new Byte((byte)(i - 128));
}
所以cache數(shù)組中存儲的就是-128到127范圍的所有數(shù)。在構(gòu)建時直接定位到具體的數(shù)組位置中去签杈,并將該位置上的數(shù)值直接返回即可瘫镇。
其余數(shù)據(jù)類型基本邏輯都差不多了,都有一個緩存值范圍答姥,如果超過了铣除,就利用構(gòu)造器直接構(gòu)造,否則直接返回緩存的對象踢涌。
拆箱
上面介紹的valueOf方法是裝箱操作的時候使用的通孽,還有一個拆箱操作,看下面這個例子:
Integer a = 100;
int b = 20;
int c = a + b;
上面代碼反編譯之后就得到:
Integer a = Integer.valueOf((int)100);
int b = 20;
int c = a.intValue() + b;
可以看到第一步進行了自動裝箱操作睁壁,在第三行中背苦,基本數(shù)據(jù)類型和包裝類型進行運算,需要將包裝類型進行拆箱操作潘明,用到了intValue方法行剂。這個方法其實在源碼中很簡單,就是一句話钳降,返回value厚宰。我們知道任何包裝類型,內(nèi)部都有一個基本數(shù)據(jù)類型的字段用于存儲對應(yīng)基本類型的值遂填,這個字段就是value铲觉。
相應(yīng)的其他包裝類型在進行拆箱的時候,都會調(diào)用對應(yīng)的xxxValue方法吓坚,例如:byteValue撵幽、shortValue等等方法。其實內(nèi)部邏輯都是一樣礁击,直接返回存儲的value值盐杂。
自動裝箱和拆箱的時機
直接賦值
這個情況其實在前面介紹自動裝箱的操作的時候,舉例代碼中就是這種情況哆窿,將一個字面量直接賦值給對應(yīng)包裝類型會觸發(fā)自動裝箱操作链烈。
函數(shù)參數(shù)
//自動拆箱
public int getNum1(Integer num) {
return num;
}
//自動裝箱
public Integer getNum2(int num) {
return num;
}
集合操作
在Java的集合中,泛型只能是包裝類型挚躯,但是我們在存儲數(shù)據(jù)的時候强衡,一般都是直接存儲對應(yīng)的基本類型數(shù)據(jù),這里就有一個自動裝箱的過程码荔。
運算符運算
上面在拆箱操作的時候利用的就是這個特性漩勤,當(dāng)基本數(shù)據(jù)類型和對應(yīng)的包裝類型進行算術(shù)運算時号涯,包裝類型會首先進行自動拆箱,然后再與基本數(shù)據(jù)類型的數(shù)據(jù)進行運算锯七。
說到運算符,這里對于自動拆箱有一個需要注意的地方:
Integer a = null;
int b = a;// int b = a.intValue();
這種情況編譯是可以通過的誉己,但是在運行的時候會拋出空指針異常眉尸,這就是自動拆箱導(dǎo)致的這種錯誤。因為自動拆箱會調(diào)用intValue方法巨双,但是此時a是null噪猾,所以會拋異常。平時在使用的時候筑累,注意非空判斷即可袱蜡。
自動裝拆箱帶來的問題
==比較
首先就是前面提到的關(guān)于==操作符的結(jié)果問題,因為自動裝箱的機制慢宗,我們不能依賴于==操作符坪蚁,它在一定范圍內(nèi)數(shù)值相同為true,但是在更多的空間中镜沽,數(shù)值相同的包裝類型對象比較的結(jié)果為false敏晤。如果需要比較缅茉,可以考慮使用equals比較或者將其轉(zhuǎn)換成對應(yīng)的基本類型再進行比較可以保證結(jié)果的一致性蔬墩。
空指針
這是上面在說到運算符的時候提到的一種情況,因為有自動拆箱的機制,如果初始的包裝類型對象為null,那么在自動拆箱的時候的就會報NullPointerException,在使用時需要格外注意惑惶,在使用之前進行非空判定鱼冀,保證程序的正常運行荸型。
內(nèi)存浪費
這里有個例子:
Integer sum = 0;
for(int i=1000; i<5000; i++){
sum+=i;
}
上面代碼中的 sum+=i 這個操作其實就是拆箱再裝箱的過程踪宠,拆箱過程是發(fā)生在相加的時候柬脸,sum本身是Integer爆价,自動拆箱成int與 i 相加憔披。將得到的結(jié)果賦值給sum的時候望门,又會進行自動裝箱,所以上面的for循環(huán)體中一句話锰霜,在編譯后會變?yōu)閮删洌?/p>
int result = sum.intValue() + i;
Integer sum = new Integer(result);
所以在進行了5000次循環(huán)后筹误,會出現(xiàn)大量的無用對象造成內(nèi)容空間的浪費癣缅,同時加重了垃圾回收的工作量纫事,所以在日常編碼過程中需要格外注意所灸,避免出現(xiàn)這種浪費現(xiàn)象爬立。
方法重載問題
最典型的就是ArrayList中出現(xiàn)的remove方法,它有remove(int index)和remove(Object obj)方法万哪,如果此時恰巧ArrayList中存儲的就是Integer元素侠驯,那么會不會出現(xiàn)混淆的情況呢?其實這個只需要做一個簡單的測試就行:
public static void test(Integer num) {
System.out.println("Integer參數(shù)的方法被調(diào)用...");
}
?
public static void test(int num) {
System.out.println("int參數(shù)的方法被調(diào)用...");
}
public static void main(String[] args) {
int i = 2;
test(i); //int參數(shù)的方法被調(diào)用...
Integer j = 4;
test(j);//Integer參數(shù)的方法被調(diào)用...
}
所以可以發(fā)現(xiàn)奕巍,當(dāng)出現(xiàn)這種情況的時候吟策,是不會發(fā)生自動裝箱和拆箱操作的〉闹梗可以正常區(qū)分檩坚。