細(xì)讀源碼之Integer

我們知道Java是面向?qū)ο蟮恼Z(yǔ)言,號(hào)稱一切皆對(duì)象吃环,但是有8種原始數(shù)據(jù)類型(boolean也颤、byte 、short模叙、char歇拆、int鞋屈、float范咨、double、long)需要除排除在外厂庇。

在面試過(guò)程中經(jīng)常會(huì)遇到渠啊,考察原始數(shù)據(jù)類型和其包裝類語(yǔ)言特性的問(wèn)題。

本文就以原始數(shù)據(jù)類型int和其包裝類Integer為例進(jìn)行講解权旷,主要包含以下幾個(gè)方面的內(nèi)容:

1.Integer的不可變性

2.自動(dòng)裝箱和自動(dòng)拆箱(boxing/unboxing)

3.Integer的緩存值

4.使用Integer注意的事項(xiàng)

一.Integer的不可變性

A.什么是不可變對(duì)象替蛉?

如果一個(gè)對(duì)象,在它創(chuàng)建完成之后拄氯,不能再改變它的狀態(tài)躲查,那么這個(gè)對(duì)象就是不可變的。

前面講解的字符串String是不可變對(duì)象译柏,另外8種包裝類型也都是不可變對(duì)象镣煮,其中自然包括Integer。

B.Integer如何實(shí)現(xiàn)不可變

1.Integer類定義使用final修飾鄙麦,不能被繼承典唇,這樣Integer的所有方法就不能被重寫;

2.存儲(chǔ)數(shù)據(jù)的字段聲明為private final int value胯府,使用private final修飾介衔,value的值構(gòu)造函數(shù)初始化后不能被修改。

C.Integer不可變帶來(lái)的好處

1.Integer只有設(shè)計(jì)成不可變的對(duì)象骂因,才能為其建立緩存值(后面會(huì)相信講解)炎咖,以達(dá)到節(jié)約內(nèi)存的目的;

2.Integer是不可變的,必然是線程安全的乘盼,這樣同一個(gè)Integer對(duì)象就可以被多個(gè)線程安全地共享急迂,而且不需要任何同步操作;

3.Integer是不可變的蹦肴,可以保證信息的安全僚碎。比如我們使用Integer來(lái)保存服務(wù)器某個(gè)服務(wù)的端口,如果我們可以輕易地把Integer對(duì)象改變?yōu)槠渌麛?shù)值阴幌,這會(huì)給產(chǎn)品的可靠性帶來(lái)嚴(yán)重的問(wèn)題勺阐。

D.舉例說(shuō)明:

?上面代碼執(zhí)行結(jié)果:

?number和numberToIncrease兩個(gè)變量的內(nèi)存示意圖,如下圖所示:

1. 執(zhí)行number = 1的時(shí)候矛双,number執(zhí)行value = 1的Integer對(duì)象渊抽;

2. 調(diào)用increase方法,number作為實(shí)參傳給numberToIncrease议忽,此時(shí) number和numberToIncrease都指向了value = 1的Integer對(duì)象懒闷;

3. 執(zhí)行numberToIncrease = numberToIncrease + 1;numberToIncrease就指向了value = 2的Integer對(duì)象栈幸,但是number的指向不變愤估,還是原來(lái)value = 1的Integer對(duì)象。

最終速址,有了上面的執(zhí)行結(jié)果玩焰。

二.自動(dòng)裝箱和自動(dòng)拆箱(boxing/unboxing)

Java中的自動(dòng)裝箱和拆箱操作是通過(guò)語(yǔ)法糖實(shí)現(xiàn)的。什么是語(yǔ)法糖芍锚?

語(yǔ)法糖昔园,是指計(jì)算機(jī)語(yǔ)言中添加的某種語(yǔ)法,這種語(yǔ)法對(duì)語(yǔ)言的功能并沒(méi)有影響并炮,但是更方便程序員使用默刚。

通常來(lái)說(shuō)使用語(yǔ)法糖能夠增加程序的可讀性,從而減少程序代碼出錯(cuò)的機(jī)會(huì)逃魄。

在講解自動(dòng)裝箱和自動(dòng)拆箱操作之前荤西,先說(shuō)明一下裝箱和拆箱的概念:

裝箱:把Java原始數(shù)據(jù)類型(如:int)轉(zhuǎn)化為其對(duì)應(yīng)的包裝類型(如:Integer)的過(guò)程我們稱為裝箱操作;

拆箱:把Java包裝類型(如:Integer)轉(zhuǎn)化為其對(duì)應(yīng)的原始數(shù)據(jù)類型(如:int)的過(guò)程我們稱為拆箱操作嗅钻。

下面舉個(gè)例子皂冰,說(shuō)明一下裝箱和拆箱的操作,例1:

1. main方法的第一行养篓,代碼中的0是int類型秃流,當(dāng)賦值給Integer類型的sum變量時(shí),調(diào)用了Integer.valueOf將int類型轉(zhuǎn)換成Integer類型柳弄,這個(gè)過(guò)程就是裝箱舶胀;

2. main方法的第二行概说,sum要進(jìn)行加法操作時(shí),Integer類型無(wú)法直接進(jìn)行加法操作嚣伐,先執(zhí)行sum.intValue()變成int后糖赔,再進(jìn)行加法操作,而轉(zhuǎn)化為int的過(guò)程就是拆箱轩端;

3. main方法的第二行放典,當(dāng)需要把加1的結(jié)果,再賦值給sum的時(shí)基茵,再次調(diào)用Integer.valueOf進(jìn)行類型轉(zhuǎn)換奋构,又進(jìn)行了一次裝箱操作幅垮。

當(dāng)然平時(shí)我們很少會(huì)寫上面那種臃腫的代碼术浪,常用的寫法如下,例2:

?但是我們查看這兩個(gè)類main方法的字節(jié)碼朝群,結(jié)果完全一致根灯,如下所示:

從上面的字節(jié)碼可以看出:

雖然例2中的代碼中沒(méi)有顯式地去調(diào)用Integer.valueOf和Integer.intValue方法径缅。

但是編譯后的class文件中,卻有這兩個(gè)方法的隱式調(diào)用烙肺,而這個(gè)隱式調(diào)用過(guò)程就是自動(dòng)裝箱和自動(dòng)拆箱的操作纳猪。

自動(dòng)裝箱:當(dāng)一個(gè)Integer類型的值,需要變成int的時(shí)候(比如要進(jìn)行加法運(yùn)算)茬高,Java編譯器會(huì)加入Integer.intValue()的方法調(diào)用兆旬,將Integer類型自動(dòng)轉(zhuǎn)換成int;

自動(dòng)拆箱:當(dāng)一個(gè)int的值怎栽,需要變成Integer的時(shí)候(比如把int類型的值賦值給Integer),Java編譯器會(huì)加入一段Integer.valueOf(int i)的方法調(diào)用宿饱,把int類型自動(dòng)轉(zhuǎn)換為Integer類型熏瞄。

三.Integer的緩存值

關(guān)于Integer的值緩存,涉及到Java 5的一個(gè)改進(jìn)谬以。在Java 5之前强饮,構(gòu)建Integer對(duì)象的傳統(tǒng)方式是,直接調(diào)用其構(gòu)造函數(shù)創(chuàng)建出一個(gè)新的對(duì)象为黎。

但是根據(jù)實(shí)踐的結(jié)果邮丰,我們發(fā)現(xiàn)大部分int數(shù)值運(yùn)算的結(jié)果都集中在有限的、較小的數(shù)值范圍內(nèi)铭乾。

因此剪廉,在Java 5在Integer類上新增了一個(gè)valueOf的靜態(tài)工廠方法,在調(diào)用它的時(shí)候會(huì)利用一個(gè)緩存機(jī)制炕檩,最終帶來(lái)了明顯的性能改進(jìn)斗蒋。

我們先看看下面的代碼,猜測(cè)一下代碼執(zhí)行的結(jié)果,代碼如下:

代碼執(zhí)行結(jié)果如下:

當(dāng)沒(méi)有讀過(guò)Integer的源碼泉沾,看到上面的結(jié)果捞蚂,是不是會(huì)很驚訝。

為什么a和b賦值為1跷究,進(jìn)行==判斷返回true姓迅,而c和d賦值為128,進(jìn)行==判斷就返回false了呢俊马?

前面了解了自動(dòng)裝箱操作后队贱,我們知道,當(dāng)把int型的值潭袱,賦值為Integer類型的時(shí)候柱嫌,會(huì)調(diào)用Integer.valueOf進(jìn)行自動(dòng)裝箱操作,奧秘應(yīng)該就是在Integer.valueOf方法中屯换,源碼如下:

當(dāng)傳入的參數(shù)i大于等于IntegerCache.low并且小于等于IntegerCache.high時(shí)编丘,則從IntegerCache.cache中取值;

而其他情況下則新創(chuàng)建一個(gè)Integer對(duì)象彤悔。

IntegerCache的low和high是多少嘉抓,還有cache又是什么呢,接著讀一下IntegerCache的源碼:

通過(guò)上面的代碼晕窑,我們得出下面結(jié)論:

1.low的值是固定為-128抑片;

2.high的默認(rèn)值是127,但是可以通過(guò)java.lang.Integer.IntegerCache.high這個(gè)property進(jìn)行設(shè)置杨赤。

最終取127和設(shè)置值中的較大值敞斋,并且取Integer.MAX_VALUE - (-low) -1和設(shè)置值中的較小值,作為最終的high的值疾牲;

3.最后根據(jù)low和high的值植捎,對(duì)cache進(jìn)行初始化,其cache[0] = -128阳柔,cache[cache.length - 1] = high焰枢。

還是上面的代碼,如果加上JVM參數(shù)-Djava.lang.Integer.IntegerCache.high=128后再次執(zhí)行舌剂,結(jié)果如下:

通過(guò)JVM參數(shù)济锄,IntegerCache緩存的最大值設(shè)置為128,128也進(jìn)行了緩存霍转,c == d就由原來(lái)的false變成了true荐绝。

四.使用Integer注意的地方

1.使用int類型替換Integer進(jìn)行數(shù)值計(jì)算

Integer類型無(wú)法直接進(jìn)行數(shù)值計(jì)算,在計(jì)算之前需要進(jìn)行拆箱變成int后進(jìn)行計(jì)算谴忧,在計(jì)算之后賦值給Integer類型的時(shí)候又要進(jìn)行裝箱操作很泊。

大量的裝箱和拆箱操作非常浪費(fèi)CPU和內(nèi)存角虫,下面代碼對(duì)比一下二者的效率,代碼如下:

上面代碼執(zhí)行結(jié)果如下所示:

computeByInt方法中委造,直接對(duì)int變量進(jìn)行操作戳鹅;

而computeByInteger方法中,sum = sum + 1昏兆,有一次自動(dòng)裝箱和一次自動(dòng)拆箱操作枫虏,極大地影響了性能。

從最終的結(jié)果來(lái)看爬虱,兩者之前有成千上萬(wàn)倍的性能差異隶债。

2.使用int[]數(shù)組替換Integer[]和ArrayList

我們知道Java的對(duì)象都是引用類型,如果是一個(gè)原始數(shù)據(jù)類型數(shù)組跑筝,它在內(nèi)存里是一段連續(xù)的內(nèi)存死讹;

而對(duì)象數(shù)組則不一樣,數(shù)據(jù)存儲(chǔ)的是引用曲梗,對(duì)象往往是分散地存儲(chǔ)在堆的不同位置赞警。

這種設(shè)計(jì)雖然帶來(lái)了極大靈活性,但是也導(dǎo)致了數(shù)據(jù)操作的低效虏两,尤其是無(wú)法充分利用現(xiàn)代CPU緩存機(jī)制愧旦。

下面舉個(gè)例子說(shuō)明二者性能的差異:

上面代碼運(yùn)行結(jié)果如下:

從運(yùn)行結(jié)果上看,sumInt方法和sumInteger兩個(gè)方法的性能差異巨大定罢,主要有兩個(gè)原因:

1. sumInteger進(jìn)行加法操作的時(shí)候笤虫,多了一次拆箱操作;

2. int[]數(shù)組內(nèi)數(shù)據(jù)存儲(chǔ)是連續(xù)的祖凫,可以充分利用現(xiàn)代CPU緩存機(jī)制琼蚯,而Integer[]中每個(gè)元素的intValue值,就分散地存儲(chǔ)在堆的不同位置蝙场,無(wú)法充分利用CPU緩存機(jī)制凌停,導(dǎo)致性能損失。

所以售滤,使用原始數(shù)據(jù)類型替換包裝類,使用數(shù)組替換動(dòng)態(tài)數(shù)組(如ArrayList)台诗,在性能極度敏感的場(chǎng)景往往具有比較大的優(yōu)勢(shì)完箩,一些追求極致性能的產(chǎn)品或者類庫(kù),會(huì)極力避免創(chuàng)建過(guò)多對(duì)象拉队。

當(dāng)然弊知,在大多數(shù)純業(yè)務(wù)功能代碼里,并沒(méi)有必要這么做粱快,還是以開發(fā)效率優(yōu)先秩彤。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末叔扼,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子漫雷,更是在濱河造成了極大的恐慌瓜富,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,402評(píng)論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件降盹,死亡現(xiàn)場(chǎng)離奇詭異与柑,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)蓄坏,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門价捧,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人涡戳,你說(shuō)我怎么就攤上這事结蟋。” “怎么了渔彰?”我有些...
    開封第一講書人閱讀 162,483評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵嵌屎,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我胳岂,道長(zhǎng)编整,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,165評(píng)論 1 292
  • 正文 為了忘掉前任乳丰,我火速辦了婚禮掌测,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘产园。我一直安慰自己汞斧,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,176評(píng)論 6 388
  • 文/花漫 我一把揭開白布什燕。 她就那樣靜靜地躺著粘勒,像睡著了一般。 火紅的嫁衣襯著肌膚如雪屎即。 梳的紋絲不亂的頭發(fā)上庙睡,一...
    開封第一講書人閱讀 51,146評(píng)論 1 297
  • 那天,我揣著相機(jī)與錄音技俐,去河邊找鬼乘陪。 笑死,一個(gè)胖子當(dāng)著我的面吹牛雕擂,可吹牛的內(nèi)容都是我干的啡邑。 我是一名探鬼主播,決...
    沈念sama閱讀 40,032評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼井赌,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼谤逼!你這毒婦竟也來(lái)了贵扰?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,896評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤流部,失蹤者是張志新(化名)和其女友劉穎戚绕,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體贵涵,經(jīng)...
    沈念sama閱讀 45,311評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡列肢,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,536評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了宾茂。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片瓷马。...
    茶點(diǎn)故事閱讀 39,696評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖跨晴,靈堂內(nèi)的尸體忽然破棺而出欧聘,到底是詐尸還是另有隱情,我是刑警寧澤端盆,帶...
    沈念sama閱讀 35,413評(píng)論 5 343
  • 正文 年R本政府宣布怀骤,位于F島的核電站,受9級(jí)特大地震影響焕妙,放射性物質(zhì)發(fā)生泄漏蒋伦。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,008評(píng)論 3 325
  • 文/蒙蒙 一焚鹊、第九天 我趴在偏房一處隱蔽的房頂上張望痕届。 院中可真熱鬧,春花似錦末患、人聲如沸研叫。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)嚷炉。三九已至,卻和暖如春探橱,著一層夾襖步出監(jiān)牢的瞬間申屹,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工隧膏, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留独柑,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,698評(píng)論 2 368
  • 正文 我出身青樓私植,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親车酣。 傳聞我的和親對(duì)象是個(gè)殘疾皇子曲稼,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,592評(píng)論 2 353

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

  • 1 前言 我們都知道贫悄,java提供了8種基本數(shù)據(jù)類型供我們使用瑞驱,此外,java還提供了這些基本數(shù)據(jù)類型所對(duì)應(yīng)的包裝...
    l1fe1閱讀 489評(píng)論 0 1
  • 1. 描述 API:int的包裝類型窄坦,內(nèi)部包含了一個(gè)int字段唤反。 關(guān)鍵字段: 2. 構(gòu)造函數(shù) 第一種 直接賦值:傳...
    Oliver_Li閱讀 230評(píng)論 0 0
  • Integer類將基礎(chǔ)類型int包裝到了對(duì)象中,形成一個(gè)引用類型鸭津。它提供的主要功能是與int相關(guān)的類型封裝與轉(zhuǎn)換彤侍。...
    LuckyBuzz閱讀 669評(píng)論 1 2
  • 1.典型回答 int 是基本數(shù)據(jù)類型(Primitive Types),是 java 的8個(gè)基本數(shù)據(jù)類型之一逆趋,ja...
    憩在河岸上的魚丶閱讀 320評(píng)論 0 2
  • Integer 本文源碼基于JDK8 Integer也是我們經(jīng)常使用的工具類盏阶、包裝類,此文主要用于記錄學(xué)習(xí)筆記闻书,主...
    luoyoub閱讀 247評(píng)論 1 0