什么是Java的自動拆裝箱

本文轉(zhuǎn)載自微信公眾號: Hollis

基本數(shù)據(jù)類型

基本類型,或者叫做內(nèi)置類型抠刺,是Java中不同于類(Class)的特殊類型摘昌。它們是我們編程中使用最頻繁的類型。
Java是一種強(qiáng)類型語言罕容,第一次申明變量必須說明數(shù)據(jù)類型,第一次變量賦值稱為變量的初始化锦秒。

Java基本類型共有八種脂崔,基本類型可以分為三類:
字符類型 char
布爾類型 boolean
整數(shù)類型 byte梧喷、shortint汇歹、long
浮點(diǎn)數(shù)類型 float偿凭、double

Java中的數(shù)值類型不存在無符號的,它們的取值范圍是固定的弯囊,不會隨著機(jī)器硬件環(huán)境或者操作系統(tǒng)的改變而改變匾嘱。

實(shí)際上,Java中還存在另外一種基本類型void霎烙,它也有對應(yīng)的包裝類 java.lang.Void悬垃,不過我們無法直接對它們進(jìn)行操作。

基本數(shù)據(jù)類型有什么好處

我們都知道在Java語言中尝蠕,new一個對象是存儲在里的看彼,我們通過棧中的引用來使用這些對象;所以闲昭,對象本身來說是比較消耗資源的。

對于經(jīng)常用到的類型鸯绿,如int等,如果我們每次使用這種變量的時候都需要new一個Java對象的話毒返,就會比較笨重舷手。

所以,和C++一樣盆赤,Java提供了基本數(shù)據(jù)類型歉眷,這種數(shù)據(jù)的變量不需要使用new創(chuàng)建,他們不會在堆上創(chuàng)建淑际,而是直接在棧內(nèi)存中存儲扇住,因此會更加高效。

整型的取值范圍

Java中的整型主要包含byte艘蹋、short、int和long這四種咱娶,表示的數(shù)字范圍也是從小到大的强品,之所以表示范圍不同主要和他們存儲數(shù)據(jù)時所占的字節(jié)數(shù)有關(guān)。

先來個簡答的科普琼了,1字節(jié)=8位(bit)夫晌。java中的整型屬于有符號數(shù)。

先來看計算中8bit可以表示的數(shù)字:

最小值:10000000 (-128)(-2^7)
最大值:01111111(127)(2^7-1)

整型的這幾個類型中所袁,

  • byte:byte用1個字節(jié)來存儲凶掰,范圍為-128(-27)到127(27-1)蜈亩,在變量初始化的時候稚配,byte類型的默認(rèn)值為0。

  • short:short用2個字節(jié)存儲港华,范圍為-32,768 (-2^15)到32,767 (2^15-1)道川,在變量初始化的時候,short類型的默認(rèn)值為0立宜,一般情況下冒萄,因?yàn)镴ava本身轉(zhuǎn)型的原因,可以直接寫為0赘理。

  • int:int用4個字節(jié)存儲宦言,范圍為-2,147,483,648 (-2^31)到2,147,483,647 (2^31-1),在變量初始化的時候商模,int類型的默認(rèn)值為0。

  • long:long用8個字節(jié)存儲蜘澜,范圍為-9,223,372,036,854,775,808 (-2^63)到9,223,372,036, 854,775,807 (2^63-1)施流,在變量初始化的時候鄙信,long類型的默認(rèn)值為0L或0l瞪醋,也可直接寫為0。

超出范圍怎么辦

上面說過了装诡,整型中银受,每個類型都有一定的表示范圍,但是鸦采,在程序中有些計算會導(dǎo)致超出表示范圍宾巍,即溢出。如以下代碼:

int j = Integer.MAX_VALUE;
int k = i + j;
System.out.println("i (" + i + ") + j (" + j + ") = k (" + k + ")");

輸出結(jié)果:i (2147483647) + j (2147483647) = k (-2)

這就是發(fā)生了溢出渔伯,溢出的時候并不會拋異常顶霞,也沒有任何提示。所以锣吼,在程序中选浑,使用同類型的數(shù)據(jù)進(jìn)行運(yùn)算的時候,一定要注意數(shù)據(jù)溢出的問題玄叠。

包裝類型

Java語言是一個面向?qū)ο蟮恼Z言古徒,但是Java中的基本數(shù)據(jù)類型卻是不面向?qū)ο蟮模@在實(shí)際使用時存在很多的不便读恃,為了解決這個不足隧膘,在設(shè)計類時為每個基本數(shù)據(jù)類型設(shè)計了一個對應(yīng)的類進(jìn)行代表崎苗,這樣八個和基本數(shù)據(jù)類型對應(yīng)的類統(tǒng)稱為包裝類(Wrapper Class)。

包裝類均位于java.lang包舀寓,包裝類和基本數(shù)據(jù)類型的對應(yīng)關(guān)系如下表所示
基本數(shù)據(jù)類型 包裝類

基本數(shù)據(jù)類型 包裝類
byte Byte
boolean Boolean
short Short
char Character
int Integer
long Long
float Float
double Double

為什么需要包裝類

很多人會有疑問胆数,既然Java中為了提高效率,提供了八種基本數(shù)據(jù)類型互墓,為什么還要提供包裝類呢必尼?

這個問題,其實(shí)前面已經(jīng)有了答案篡撵,因?yàn)镴ava是一種面向?qū)ο笳Z言判莉,很多地方都需要使用對象而不是基本數(shù)據(jù)類型。比如育谬,在集合類中券盅,我們是無法將int 、double等類型放進(jìn)去的膛檀。因?yàn)榧系娜萜饕笤厥荗bject類型锰镀。

為了讓基本類型也具有對象的特征,就出現(xiàn)了包裝類型咖刃,它相當(dāng)于將基本類型“包裝起來”泳炉,使得它具有了對象的性質(zhì),并且為其添加了屬性和方法嚎杨,豐富了基本類型的操作花鹅。

拆箱與裝箱

那么,有了基本數(shù)據(jù)類型和包裝類枫浙,肯定有些時候要在他們之間進(jìn)行轉(zhuǎn)換刨肃。比如把一個基本數(shù)據(jù)類型的int轉(zhuǎn)換成一個包裝類型的Integer對象。

我們認(rèn)為包裝類是對基本類型的包裝箩帚,所以真友,把基本數(shù)據(jù)類型轉(zhuǎn)換成包裝類的過程就是打包裝,英文對應(yīng)于boxing膏潮,中文翻譯為裝箱锻狗。

反之,把包裝類轉(zhuǎn)換成基本數(shù)據(jù)類型的過程就是拆包裝焕参,英文對應(yīng)于unboxing轻纪,中文翻譯為拆箱。

在Java SE5之前叠纷,要進(jìn)行裝箱刻帚,可以通過以下代碼:
Integer i = new Integer(10)

自動拆箱與自動裝箱

在Java SE5中,為了減少開發(fā)人員的工作涩嚣,Java提供了自動拆箱與自動裝箱功能崇众。
自動裝箱: 就是將基本數(shù)據(jù)類型自動轉(zhuǎn)換成對應(yīng)的包裝類掂僵。

自動拆箱:就是將包裝類自動轉(zhuǎn)換成對應(yīng)的基本數(shù)據(jù)類型。

Integer i =10;  //自動裝箱
int b= i;     //自動拆箱

Integer i=10 可以替代 Integer i = new Integer(10);顷歌,這就是因?yàn)镴ava幫我們提供了自動裝箱的功能锰蓬,不需要開發(fā)者手動去new一個Integer對象。

自動拆箱與自動裝箱的原理

我們有以下自動拆裝箱的代碼:

    Integer integer=1; //裝箱
    int i=integer; //拆箱
}

對以上代碼進(jìn)行反編譯后可以得到以下代碼:

    Integer integer=Integer.valueOf(1); 
    int i=integer.intValue(); 
}

從上面反編譯后的代碼可以看出眯漩,int的自動裝箱都是通過Integer.valueOf()方法來實(shí)現(xiàn)的芹扭,Integer的自動拆箱都是通過integer.intValue來實(shí)現(xiàn)的。如果讀者感興趣赦抖,可以試著將八種類型都反編譯一遍 舱卡,你會發(fā)現(xiàn)以下規(guī)律:

自動裝箱都是通過包裝類的valueOf()方法來實(shí)現(xiàn)的.自動拆箱都是通過包裝類對象的xxxValue()來實(shí)現(xiàn)的。

哪些地方會自動拆箱裝箱

我們了解過原理之后队萤,在來看一下轮锥,什么情況下,Java會幫我們進(jìn)行自動拆裝箱要尔。前面提到的變量的初始化和賦值的場景就不介紹了舍杜,那是最簡單的也最容易理解的。
我們主要來看一下盈电,那些可能被忽略的場景蝴簇。

1.將基本數(shù)據(jù)類型放入集合

我們知道,Java中的集合類只能接收對象類型匆帚,那么以下代碼為什么會不報錯呢?

List<Integer> li = new ArrayList<>();
for (int i = 1; i < 50; i ++){
    li.add(i);
}

將上面代碼進(jìn)行反編譯旁钧,可以得到以下代碼:

List<Integer> li = new ArrayList<>();
for (int i = 1; i < 50; i += 2){
    li.add(Integer.valueOf(i));
}

以上吸重,我們可以得出結(jié)論,當(dāng)我們把基本數(shù)據(jù)類型放入集合類中的時候歪今,會進(jìn)行自動裝箱嚎幸。

2.包裝類型和基本類型的大小比較

有沒有人想過,當(dāng)我們對Integer對象與基本類型進(jìn)行大小比較的時候寄猩,實(shí)際上比較的是什么內(nèi)容呢嫉晶?看以下代碼:

Integer a=1;
System.out.println(a==1?"等于":"不等于");
Boolean bool=false;
System.out.println(bool?"真":"假");

對以上代碼進(jìn)行反編譯,得到以下代碼:

Integer a=1;
System.out.println(a.intValue()==1?"等于":"不等于");
Boolean bool=false;
System.out.println(bool.booleanValue?"真":"假");

可以看到田篇,包裝類與基本數(shù)據(jù)類型進(jìn)行比較運(yùn)算替废,是先將包裝類進(jìn)行拆箱成基本數(shù)據(jù)類型,然后進(jìn)行比較的泊柬。

3.包裝類型的運(yùn)算

有沒有人想過椎镣,當(dāng)我們對Integer對象進(jìn)行四則運(yùn)算的時候,是如何進(jìn)行的呢兽赁?看以下代碼:

Integer i = 10;
Integer j = 20;
System.out.println(i+j);

反編譯后代碼如下:

Integer i = Integer.valueOf(10);
Integer j = Integer.valueOf(20);
System.out.println(i.intValue() + j.intValue());

我們發(fā)現(xiàn)状答,兩個包裝類型之間的運(yùn)算冷守,會被自動拆箱成基本類型進(jìn)行。

4.三目運(yùn)算符

這是很多人不知道的一個場景惊科,作者也是一次線上的血淋淋的Bug發(fā)生后才了解到的一種案例拍摇。看一個簡單的三目運(yùn)算符的代碼:

boolean flag = true;
Integer i = 0;
int j = 1;
int k = flag ? i : j;

很多人不知道馆截,其實(shí)在int k = flag ? i : j;這一行充活,會發(fā)生自動拆箱。反編譯后代碼如下:

boolean flag = true;
Integer i = Integer.valueOf(0);
int j = 1;
int k = flag ? i.intValue() : j;

這其實(shí)是三目運(yùn)算符的語法規(guī)范:當(dāng)?shù)诙镞洌谌徊僮鲾?shù)分別為基本類型和對象時堪唐,其中的對象就會拆箱為基本類型進(jìn)行操作。

因?yàn)槔又校?code>flag ? i : j;片段中翎蹈,第二段的i是一個包裝類型的對象淮菠,而第三段的j是一個基本類型,所以會對包裝類進(jìn)行自動拆箱荤堪。如果這個時候i的值為null合陵,那么就會發(fā)生NPE。(自動拆拆箱導(dǎo)致空指針異常)

5.函數(shù)參數(shù)與返回值

這個比較容易理解澄阳,直接上代碼了:

//自動拆箱
public int getNum1(Integer num) {
 return num;
}
//自動裝箱
public Integer getNum2(int num) {
 return num;
}

自動拆裝箱與緩存

Java SE的自動拆裝箱還提供了一個和緩存有關(guān)的功能拥知,我們先來看以下代碼,猜測一下輸出結(jié)果:

public static void main(String... strings) {

    Integer integer1 = 3;
    Integer integer2 = 3;

    if (integer1 == integer2)
        System.out.println("integer1 == integer2");
    else
        System.out.println("integer1 != integer2");

    Integer integer3 = 300;
    Integer integer4 = 300;

    if (integer3 == integer4)
        System.out.println("integer3 == integer4");
    else
        System.out.println("integer3 != integer4");
}

我們普遍認(rèn)為上面的兩個判斷的結(jié)果都是false碎赢。雖然比較的值是相等的低剔,但是由于比較的是對象,而對象的引用不一樣肮塞,所以會認(rèn)為兩個if判斷都是false的襟齿。

在Java中,==比較的是對象應(yīng)用枕赵,而equals比較的是值猜欺。

所以,在這個例子中拷窜,不同的對象有不同的引用开皿,所以在進(jìn)行比較的時候都將返回false。奇怪的是篮昧,這里兩個類似的if條件判斷返回不同的布爾值赋荆。

上面這段代碼真正的輸出結(jié)果:

integer1 == integer2
integer3 != integer4

原因就和Integer中的緩存機(jī)制有關(guān)。在Java 5中恋谭,在Integer的操作上引入了一個新功能來節(jié)省內(nèi)存和提高性能糠睡。整型對象通過使用相同的對象引用實(shí)現(xiàn)了緩存和重用。

適用于整數(shù)值區(qū)間-128 至 +127疚颊。
只適用于自動裝箱狈孔。使用構(gòu)造函數(shù)創(chuàng)建對象不適用信认。

具體的代碼實(shí)現(xiàn)可以閱讀Java中整型的緩存機(jī)制一文,這里不再闡述均抽。

我們只需要知道嫁赏,當(dāng)需要進(jìn)行自動裝箱時,如果數(shù)字在-128至127之間時油挥,會直接使用緩存中的對象潦蝇,而不是重新創(chuàng)建一個對象。

其中的javadoc詳細(xì)的說明了緩存支持-128到127之間的自動裝箱過程深寥。最大值127可以通過
-XX:AutoBoxCacheMax=size修改

實(shí)際上這個功能在Java 5中引入的時候,范圍是固定的-128 至 +127攘乒。后來在Java 6中,可以通過java.lang.Integer.IntegerCache.high設(shè)置最大值惋鹅。

這使我們可以根據(jù)應(yīng)用程序的實(shí)際情況靈活地調(diào)整來提高性能则酝。到底是什么原因選擇這個-128到127范圍呢?因?yàn)檫@個范圍的數(shù)字是最被廣泛使用的闰集。 在程序中沽讹,第一次使用Integer的時候也需要一定的額外時間來初始化這個緩存。

在Boxing Conversion部分的Java語言規(guī)范(JLS)規(guī)定如下:
如果一個變量p的值是:

-128至127之間的整數(shù)(§3.10.1)

true 和 false的布爾值 (§3.10.3)

‘\u0000’至 ‘\u007f’之間的字符(§3.10.4)

范圍內(nèi)的時武鲁,將p包裝成a和b兩個對象時爽雄,可以直接使用a==b判斷a和b的值是否相等。

自動拆裝箱帶來的問題

當(dāng)然沐鼠,自動拆裝箱是一個很好的功能挚瘟,大大節(jié)省了開發(fā)人員的精力,不再需要關(guān)心到底什么時候需要拆裝箱饲梭。但是刽沾,他也會引入一些問題。

包裝對象的數(shù)值比較排拷,不能簡單的使用==,雖然-128到127之間的數(shù)字可以锅尘,但是這個范圍之外還是需要使用equals比較监氢。
前面提到,有些場景會進(jìn)行自動拆裝箱藤违,同時也說過浪腐,由于自動拆箱,如果包裝類對象為null顿乒,那么自動拆箱時就有可能拋出NPE议街。
如果一個for循環(huán)中有大量拆裝箱操作,會浪費(fèi)很多資源璧榄。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末特漩,一起剝皮案震驚了整個濱河市吧雹,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌涂身,老刑警劉巖雄卷,帶你破解...
    沈念sama閱讀 217,542評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異蛤售,居然都是意外死亡丁鹉,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評論 3 394
  • 文/潘曉璐 我一進(jìn)店門悴能,熙熙樓的掌柜王于貴愁眉苦臉地迎上來揣钦,“玉大人,你說我怎么就攤上這事漠酿》氚迹” “怎么了?”我有些...
    開封第一講書人閱讀 163,912評論 0 354
  • 文/不壞的土叔 我叫張陵记靡,是天一觀的道長谈竿。 經(jīng)常有香客問我,道長摸吠,這世上最難降的妖魔是什么空凸? 我笑而不...
    開封第一講書人閱讀 58,449評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮寸痢,結(jié)果婚禮上呀洲,老公的妹妹穿的比我還像新娘。我一直安慰自己啼止,他們只是感情好道逗,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,500評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著献烦,像睡著了一般滓窍。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上巩那,一...
    開封第一講書人閱讀 51,370評論 1 302
  • 那天吏夯,我揣著相機(jī)與錄音,去河邊找鬼即横。 笑死噪生,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的东囚。 我是一名探鬼主播跺嗽,決...
    沈念sama閱讀 40,193評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了桨嫁?” 一聲冷哼從身側(cè)響起植兰,我...
    開封第一講書人閱讀 39,074評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎瞧甩,沒想到半個月后钉跷,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,505評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡肚逸,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,722評論 3 335
  • 正文 我和宋清朗相戀三年爷辙,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片朦促。...
    茶點(diǎn)故事閱讀 39,841評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡膝晾,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出务冕,到底是詐尸還是另有隱情血当,我是刑警寧澤,帶...
    沈念sama閱讀 35,569評論 5 345
  • 正文 年R本政府宣布禀忆,位于F島的核電站臊旭,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏箩退。R本人自食惡果不足惜离熏,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,168評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望戴涝。 院中可真熱鬧滋戳,春花似錦、人聲如沸啥刻。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,783評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽可帽。三九已至娄涩,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間映跟,已是汗流浹背钝满。 一陣腳步聲響...
    開封第一講書人閱讀 32,918評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留申窘,地道東北人。 一個月前我還...
    沈念sama閱讀 47,962評論 2 370
  • 正文 我出身青樓孔轴,卻偏偏與公主長得像剃法,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子路鹰,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,781評論 2 354