淺談Java自動(dòng)裝箱與拆箱及其陷阱
在本文中,筆者向大家介紹下Java中一個(gè)非常重要也非常有趣的特性,就是自動(dòng)裝箱與拆箱,并從源碼中解讀自動(dòng)裝箱與拆箱的原理,同時(shí)這種特性也留有一個(gè)陷阱。開(kāi)發(fā)者如果不注意,就會(huì)很容易跌入這個(gè)陷阱。
自動(dòng)裝箱(Autoboxing)
定義
大家在平時(shí)編寫(xiě)Java程序時(shí),都常常以以下方式來(lái)定義一個(gè)Integer對(duì)象:
Integer i=100;
從上面的代碼中,大家可以得知,i為一個(gè)Integer類型的引用,100為Java中的基礎(chǔ)數(shù)據(jù)類型(primitive data type)。而這種直接將一個(gè)基礎(chǔ)數(shù)據(jù)類型傳給其相應(yīng)的封裝類(wrapper class)的做法,便是自動(dòng)裝箱(Autoboxing)。
在jdk 1.5中,自動(dòng)裝箱首次被引入。而在jdk 1.5之前,如果你想要定義一個(gè)value為100的Integer對(duì)象,則需要這樣做:
Integer i=new Integer (100);
原理
我們?cè)谝陨洗a“Integer i=100;”處打一個(gè)斷點(diǎn),跟蹤一下。
接下來(lái),我們可以看到,程序跳轉(zhuǎn)到了Integer類的valueOf(int i)方法中
/** * Returns a <tt>Integer</tt> instance representing the specified * <tt>int</tt> value. * If a new <tt>Integer</tt> instance is not required, this method * should generally be used in preference to the constructor * {@link #Integer(int)}, as this method is likely to yield * significantly better space and time performance by caching * frequently requested values. * * @param i an <code>int</code> value. * @return a <tt>Integer</tt> instance representing <tt>i</tt>. * @since 1.5 */ public static Integer valueOf(int i) { if(i >= -128 && i <= IntegerCache.high) return IntegerCache.cache[i + 128]; else return new Integer(i); }
換句話說(shuō),裝箱就是jdk自己幫你完成了調(diào)用Integer.valueOf(100)。
拆箱(Unboxing)
定義
Integer integer100=100; int int100=integer100;
從上面的代碼中,大家可看出integer100為一個(gè)Integer類型的引用,int100為一個(gè)int類型的原始數(shù)據(jù)類型。但是,我們可以將一個(gè)Integer類型的對(duì)象賦值給其相應(yīng)原始數(shù)據(jù)類型的變量。這便是拆箱。
拆箱與裝箱是相反的操作。裝箱是將一個(gè)原始數(shù)據(jù)類型賦值給相應(yīng)封裝類的變量。而拆箱則是將一個(gè)封裝類的變量賦值給相應(yīng)原始數(shù)據(jù)類型的變量。裝箱、拆箱的名字也取得相當(dāng)貼切。
原理
筆者相信大家也都猜到了,拆箱過(guò)程中jdk為我們做了什么。我們還是通過(guò)實(shí)驗(yàn)來(lái)證明我們的猜想吧。
在以上代碼的第二行代碼打上斷點(diǎn),即在“int int100=integer100;”上打上斷點(diǎn),跟蹤一下。
我們可以看到,程序跳轉(zhuǎn)到了Integer的intValue()方法。
/** * Returns the value of this <code>Integer</code> as an * <code>int</code>. */ public int intValue() { return value; }
也就是,jdk幫我們完成了對(duì)intValue()方法的調(diào)用。對(duì)于以上的實(shí)驗(yàn)而言,便是調(diào)用integer100的intValue()方法,將其返回值賦給了int100。
擴(kuò)展
實(shí)驗(yàn)1
Integer integer400=400; int int400=400; System.out.println(integer400==int400);
在以上代碼的第三行中,integer400與int400執(zhí)行了==運(yùn)行。而這兩個(gè)是不同類型的變量,到底是integer400拆箱了,還是int400裝箱了呢?運(yùn)行結(jié)果是什么呢?
==運(yùn)算是判斷兩個(gè)對(duì)象的地址是否相等或者判斷兩個(gè)基礎(chǔ)數(shù)據(jù)類型的值是否相等。所以,大家很容易推測(cè)到,如果integer400拆箱了,則說(shuō)明對(duì)比的是兩個(gè)基礎(chǔ)類型的值,那此時(shí)必然相等,運(yùn)行結(jié)果為true;如果int400裝箱了,則說(shuō)明對(duì)比的是兩個(gè)對(duì)象的地址是否相等,那此時(shí)地址必然不相等,運(yùn)行結(jié)果為false。(至于為什么筆者對(duì)它們賦值為400,就是后面將要講到的陷阱有關(guān))。
我們實(shí)際的運(yùn)行結(jié)果為true。所以是integer400拆箱了。對(duì)代碼跟蹤的結(jié)果也證明這一點(diǎn)。
實(shí)驗(yàn)2
Integer integer100=100; int int100=100; System.out.println(integer100.equals(int100));
在以上代碼的第三行中,integer100的方法equals的參數(shù)為int100。我們知道equals方法的參數(shù)為Object,而不是基礎(chǔ)數(shù)據(jù)類型,因而在這里必然是int100裝箱了。對(duì)代碼跟蹤的結(jié)果也證明了這一點(diǎn)。
其實(shí),如果一個(gè)方法中參數(shù)類型為原始數(shù)據(jù)類型,所傳入的參數(shù)類型為其封裝類,則會(huì)自動(dòng)對(duì)其進(jìn)行拆箱;相應(yīng)地,如果一個(gè)方法中參數(shù)類型為封裝類型,所傳入的參數(shù)類型為其原始數(shù)據(jù)類型,則會(huì)自動(dòng)對(duì)其進(jìn)行裝箱。
實(shí)驗(yàn)3
Integer integer100 = 100; int int100 = 100; Long long200 = 200l; System.out.println(integer100 + int100); System.out.println(long200 == (integer100 + int100)); System.out.println(long200.equals(integer100 + int100));
在第一個(gè)實(shí)驗(yàn)中,我們已經(jīng)得知,當(dāng)一個(gè)基礎(chǔ)數(shù)據(jù)類型與封裝類進(jìn)行==運(yùn)算時(shí),會(huì)將封裝類進(jìn)行拆箱。那如果+、-、*、/呢?我們?cè)谶@個(gè)實(shí)驗(yàn)中,就可知道。
如果+運(yùn)算,會(huì)將基礎(chǔ)數(shù)據(jù)類型裝箱,那么:
•第4行中,integer100+int100就會(huì)得到一個(gè)類型為Integer且value為200的對(duì)象o,并執(zhí)行這個(gè)對(duì)象的toString()方法,并輸出”200”;
•第5行中,integer100+int100就會(huì)得到一個(gè)類型為Integer且value為200的對(duì)象o,==運(yùn)算將這個(gè)對(duì)象與long200對(duì)象進(jìn)行對(duì)比,顯然,將會(huì)輸出false;
•第6行中,integer100+int100就會(huì)得到一個(gè)類型為Integer且value為200的對(duì)象o,Long的equals方法將long200與o對(duì)比,因?yàn)閮啥际遣煌愋偷姆庋b類,因而輸出false;
如果+運(yùn)算,會(huì)將封裝類進(jìn)行拆箱,那么:
•第4行中,integer100+int100就會(huì)得到一個(gè)類型為int且value為200的基礎(chǔ)數(shù)據(jù)類型b,再將b進(jìn)行裝箱得到o,執(zhí)行這個(gè)對(duì)象的toString()方法,并輸出”200”;
•第5行中,integer100+int100就會(huì)得到一個(gè)類型為int且value為200的基礎(chǔ)數(shù)據(jù)類型b1,==運(yùn)算將long200進(jìn)行拆箱得到b2,顯然b1==b2,輸出true;
•第6行中,integer100+int100就會(huì)得到一個(gè)類型為int且value為200的基礎(chǔ)數(shù)據(jù)類型b,Long的equals方法將b進(jìn)行裝箱,但裝箱所得到的是類型為Integer的對(duì)象o,因?yàn)閛與long200為不同的類型的對(duì)象,所以輸出false;
程序運(yùn)行的結(jié)果為:
200
true
false
因而,第二種推測(cè)是正確,即在+運(yùn)算時(shí),會(huì)將封裝類進(jìn)行拆箱。
陷阱
陷阱1
Integer integer100=null;
int int100=integer100;
這兩行代碼是完全合法的,完全能夠通過(guò)編譯的,但是在運(yùn)行時(shí),就會(huì)拋出空指針異常。其中,integer100為Integer類型的對(duì)象,它當(dāng)然可以指向null。但在第二行時(shí),就會(huì)對(duì)integer100進(jìn)行拆箱,也就是對(duì)一個(gè)null對(duì)象執(zhí)行intValue()方法,當(dāng)然會(huì)拋出空指針異常。所以,有拆箱操作時(shí)一定要特別注意封裝類對(duì)象是否為null。
陷阱2
Integer i1=100;
Integer i2=100;
Integer i3=300;
Integer i4=300;
System.out.println(i1==i2);
System.out.println(i3==i4);
因?yàn)閕1、i2、i3、i4都是Integer類型的,所以我們想,運(yùn)行結(jié)果應(yīng)該都是false。但是,真實(shí)的運(yùn)行結(jié)果為“System.out.println(i1==i2);”為 true,但是“System.out.println(i3==i4);”為false。也就意味著,i1與i2這兩個(gè)Integer類型的引用指向了同一個(gè)對(duì)象,而i3與i4指向了不同的對(duì)象。為什么呢?不都是調(diào)用Integer.valueOf(int i)方法嗎?
讓我們?cè)倏纯碔nteger.valueOf(int i)方法。
/** * Returns a <tt>Integer</tt> instance representing the specified * <tt>int</tt> value. * If a new <tt>Integer</tt> instance is not required, this method * should generally be used in preference to the constructor * {@link #Integer(int)}, as this method is likely to yield * significantly better space and time performance by caching * frequently requested values. * * @param i an <code>int</code> value. * @return a <tt>Integer</tt> instance representing <tt>i</tt>. * @since 1.5 */ public static Integer valueOf(int i) { if(i >= -128 && i <= IntegerCache.high) return IntegerCache.cache[i + 128]; else return new Integer(i); }
我們可以看到當(dāng)i>=-128且i<=IntegerCache.high時(shí),直接返回IntegerCache.cache[i + 128]。其中,IntegerCache為Integer的內(nèi)部靜態(tài)類,其原碼如下:
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() {} }
我們可以清楚地看到,IntegerCache有靜態(tài)成員變量cache,為一個(gè)擁有256個(gè)元素的數(shù)組。在IntegerCache中也對(duì)cache進(jìn)行了初始化,即第i個(gè)元素是值為i-128的Integer對(duì)象。而-128至127是最常用的Integer對(duì)象,這樣的做法也在很大程度上提高了性能。也正因?yàn)槿绱?,“Integeri1=100;Integer i2=100;”,i1與i2得到是相同的對(duì)象。
對(duì)比擴(kuò)展中的第二個(gè)實(shí)驗(yàn),我們得知,當(dāng)封裝類與基礎(chǔ)類型進(jìn)行==運(yùn)行時(shí),封裝類會(huì)進(jìn)行拆箱,拆箱結(jié)果與基礎(chǔ)類型對(duì)比值;而兩個(gè)封裝類進(jìn)行==運(yùn)行時(shí),與其它的對(duì)象進(jìn)行==運(yùn)行一樣,對(duì)比兩個(gè)對(duì)象的地址,也即判斷是否兩個(gè)引用是否指向同一個(gè)對(duì)象。
以上這篇淺談Java自動(dòng)裝箱與拆箱及其陷阱就是小編分享給大家的全部?jī)?nèi)容了,希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
@Valid 校驗(yàn)無(wú)效,BindingResult未獲得錯(cuò)誤的解決
這篇文章主要介紹了@Valid 校驗(yàn)無(wú)效,BindingResult未獲得錯(cuò)誤的解決,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-10-10java反射調(diào)用方法NoSuchMethodException的解決方案
這篇文章主要介紹了java反射調(diào)用方法NoSuchMethodException的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-03-03關(guān)于java.math.BigDecimal比較大小問(wèn)題
這篇文章主要介紹了關(guān)于java.math.BigDecimal比較大小問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-07-07- Spring事務(wù)的本質(zhì)就是對(duì)數(shù)據(jù)庫(kù)事務(wù)的支持,沒(méi)有數(shù)據(jù)庫(kù)事務(wù),Spring是無(wú)法提供事務(wù)功能的。Spring只提供統(tǒng)一的事務(wù)管理接口,具體實(shí)現(xiàn)都是由數(shù)據(jù)庫(kù)自己實(shí)現(xiàn)的,Spring會(huì)在事務(wù)開(kāi)始時(shí),根據(jù)當(dāng)前設(shè)置的隔離級(jí)別,調(diào)整數(shù)據(jù)庫(kù)的隔離級(jí)別,由此保持一致2022-07-07
Java實(shí)現(xiàn)讀取文章中重復(fù)出現(xiàn)的中文字符串
本文主要介紹了Java實(shí)現(xiàn)讀取文章中重復(fù)出現(xiàn)的中文字符串的方法。具有很好的參考價(jià)值。下面跟著小編一起來(lái)看下吧2017-03-03SpringBoot+mybatis+thymeleaf實(shí)現(xiàn)登錄功能示例
這篇文章主要介紹了SpringBoot+mybatis+thymeleaf實(shí)現(xiàn)登錄功能示例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-07-07