Java 自動裝箱與拆箱

什么的拆裝箱

我第一次聽到這個概念是一臉懵逼的,而其實最常使用的地方就是我們熟悉的包裝類的使用中.比如將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ū)分檩坚。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市诅福,隨后出現(xiàn)的幾起案子匾委,更是在濱河造成了極大的恐慌,老刑警劉巖氓润,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件赂乐,死亡現(xiàn)場離奇詭異,居然都是意外死亡咖气,警方通過查閱死者的電腦和手機挨措,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來崩溪,“玉大人浅役,你說我怎么就攤上這事×嫖ǎ” “怎么了担租?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長抵怎。 經(jīng)常有香客問我奋救,道長岭参,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任尝艘,我火速辦了婚禮演侯,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘背亥。我一直安慰自己秒际,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布狡汉。 她就那樣靜靜地躺著娄徊,像睡著了一般。 火紅的嫁衣襯著肌膚如雪盾戴。 梳的紋絲不亂的頭發(fā)上寄锐,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天,我揣著相機與錄音尖啡,去河邊找鬼橄仆。 笑死,一個胖子當(dāng)著我的面吹牛衅斩,可吹牛的內(nèi)容都是我干的盆顾。 我是一名探鬼主播,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼畏梆,長吁一口氣:“原來是場噩夢啊……” “哼您宪!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起奠涌,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤蚕涤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后铣猩,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體揖铜,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年达皿,在試婚紗的時候發(fā)現(xiàn)自己被綠了天吓。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡峦椰,死狀恐怖龄寞,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情汤功,我是刑警寧澤物邑,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響色解,放射性物質(zhì)發(fā)生泄漏茂嗓。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一科阎、第九天 我趴在偏房一處隱蔽的房頂上張望述吸。 院中可真熱鬧,春花似錦锣笨、人聲如沸蝌矛。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽入撒。三九已至,卻和暖如春椭岩,著一層夾襖步出監(jiān)牢的瞬間茅逮,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工簿煌, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人鉴吹。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓姨伟,卻偏偏與公主長得像,于是被迫代替她去往敵國和親豆励。 傳聞我的和親對象是個殘疾皇子夺荒,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,786評論 2 345

推薦閱讀更多精彩內(nèi)容