那我們就看一下這道面試題是什么呢龄毡?差不多是這樣子的面試題
題目的意思是:定義了兩個Integer類型變量赎瑰,通過swap方法交換這兩個變量的值薄声。
看似簡單的題目当船,是不是不知道從何下手,我猜想有些大家第一想到的是這樣的解法:來看代碼:
運行結(jié)果如下:
從結(jié)果來看是錯誤的默辨,不能解決我們的問題德频。為什么?
在分析之前缩幸,我們先介紹一下Java訪問對象的方式壹置。在 Java 堆中還必須包含能查找到此對象類型數(shù)據(jù)(如對象類型、父類表谊、 實現(xiàn)的接口钞护、方法等)的地址信息,這些類型數(shù)據(jù)則存儲在方法區(qū)中爆办。
既然java棧中的是對象的引用难咕,那么我們?nèi)绾问褂脤ο竽牵髁鞯脑L問方式有兩種:使用句柄和直接指針距辆。
(1)使用句柄:
如果使用句柄訪問方式余佃, Java 堆中將會劃分出一塊內(nèi)存來作為句柄池,reference 中存儲的就是對象的句柄地址跨算,而句柄中包含了對象實例數(shù)據(jù)和類型數(shù)據(jù)各自的具體地址信息咙冗,如圖:
(2)直接指針
如果使用直接指針訪問方式, Java 堆對象的布局中就必須考慮如何放置訪問類型數(shù)據(jù)的相關(guān)信息漂彤, reference 中直接存儲的就是對象地址雾消,如圖:
這兩種對象的訪問方式各有優(yōu)勢,使用句柄訪問方式的最大好處就是 reference 中存儲的是穩(wěn)定的句柄地址挫望,在對象被移動(垃圾收集時移動對象是非常普遍的行為)時只會改變句柄中的實例數(shù)據(jù)指針立润,而 reference 本身不需要被修改。
使用直接指針訪問方式的最大好處就是速度更快媳板,它節(jié)省了一次指針定位的時間開銷桑腮,由于對象的訪問在 Java 中非常頻繁,因此這類開銷積少成多后也是一項非瞅刃遥可觀的執(zhí)行成本破讨。
接著我們回到正題丛晦,這里也是今天要講的第一個知識點:Java的傳值在java中,有兩種傳值方式:一種是按值傳遞提陶,一種是引用傳遞烫沙!
那么,按值傳遞意味著將當(dāng)前的參數(shù)傳遞給方法的時候隙笆,方法中的變量接收的是傳過來變量的副本值(相當(dāng)于拷貝了一份值)锌蓄,因此,我們修改了方法里面的變量的值撑柔,并不會改變外面變量的值瘸爽。
引用傳遞:傳遞的是指向值的地址的指針
那么,請問大家铅忿,這里是按值傳遞還是引用傳遞剪决?好,老司機告訴你們檀训,這里是按值傳遞昼捍,為什么?Integer不是對象嗎肢扯? 對象傳遞不是傳遞的指針嗎妒茬?大家有沒有去看過Integer類的源碼,看看這個類是怎么定義的蔚晨,我們來看下乍钻,實際上面Integer使用的final定義的,也就意味著通過Integer實例化的對象是不能改變的铭腕,跟String是不是差不多银择。所以這里的話,是傳遞的值累舷,我們來畫下圖:
那么浩考,我們首先看一下Java運行時數(shù)據(jù)區(qū)域:
我們一般在開發(fā)中認(rèn)為JVM不過有堆和棧兩部分組成,但是實際的Java 虛擬機在執(zhí)行 Java 程序的過程中會把它所管理的內(nèi)存劃分為若干個不同的數(shù)據(jù)區(qū)域被盈。這些區(qū)域都有各自的用途析孽,以及創(chuàng)建和銷毀的時間,有的區(qū)域隨著虛擬機進程的啟動而存在只怎,有些區(qū)域則是依賴用戶線程的啟動和結(jié)束而建立和銷毀袜瞬。如下圖:
Java中的內(nèi)存主要分為兩塊把:堆和棧,棧存儲變量本身身堡,堆存儲對象的值邓尤,然后通過棧執(zhí)行堆內(nèi)存地址來建立關(guān)系。
通過swap方法后:意味著,會同樣創(chuàng)建兩個變量num1和num2,他們的值是剛剛拷貝過來的ab的值汞扎,此時內(nèi)存中時怎么變化的呢:
大家季稳,知道為什么會有地址指針這個東西,主要是我們的堆內(nèi)存他主要是存儲的是一些對象澈魄,對象是最占內(nèi)存的景鼠,為了能夠節(jié)省對內(nèi)從的空間,就出現(xiàn)了這種概念一忱。好,講到這里谭确,至少大家應(yīng)該清楚了一點:引用傳遞和按值傳遞的不同帘营。推薦一個學(xué)Java的學(xué)習(xí)裙【六七八,二四一逐哈,五六三】芬迄,無論你是大牛還是小白,是想轉(zhuǎn)行還是想入行都可以來了解一起進步一起學(xué)習(xí)昂秃!裙內(nèi)有開發(fā)工具禀梳,很多干貨和技術(shù)資料分享
我們再來看,這個Integer他內(nèi)部是如何賦值的肠骆,我們來看下:進入Integer類Ctrl+o搜索Integer構(gòu)造方法:
然后我們發(fā)現(xiàn)這個value定義的是final類型的:
如果他有一個setValue()的方法的話算途,那么我們是是不是可以通過這個方法來改變值,但是Integer并沒有提供蚀腿。也就是說這種方法是行不通的嘴瓤,好,那么我們今天講到第二個知識點:反射有沒有人在做這個題目的時候有沒有想過用反射來實現(xiàn)莉钙?
有想過的廓脆,看有多少人有往這個方面去想,我們剛剛看到Integer類中存在一個value值變量嗎磁玉?對吧停忿,所以我們需要拿到這個value變量然后來改變他的值,對吧蚊伞,那么我們怎么來做席赂,我們可以通過反射的方式拿到這個變量,這個Filed时迫,然后去改變他的值氧枣,對吧。我們來看下怎么寫:
理論上來說别垮,這種方式是一定能夠?qū)崿F(xiàn)我們的要求的.Run下:報錯:“Class com.edu.example.test.Test can not access a member of class java.lang.Integer with modifiers "private final"”
報錯了便监,是不是,那么這又是另外一個知識點:
私有的成員屬性是不能通過反射來賦值的!
那么烧董,如果要強攻毁靶,怎么辦?實際上面逊移,在java反射中预吆,提供了一個叫設(shè)置訪問權(quán)限的東西,我們進入Field類中看下:
然后他里面有一個setAccessible的方法:
這個方法就是用來設(shè)置成員屬性訪問權(quán)限的胳泉。我們看到最后是給obj.override=flag
那么我們在回過頭來看下拐叉,F(xiàn)ield的set方法:
這幾行代碼意味著,也就是說扇商,如果override是false,就會調(diào)用Reflection.quickCheckMemberAccess(clazz, modifiers)來檢查成員屬性的訪問權(quán)限凤瘦。
所以說,我們再來看案铺,這個時候是不是就可以通過設(shè)置setAccessible(true)為true來標(biāo)志不需要訪問權(quán)限的檢查蔬芥。這樣就可以修改value的值了。對不控汉。我們來試驗下:
好笔诵,大家覺得這樣沒問題,結(jié)果如下:
結(jié)果是姑子,a的值確實變了乎婿,但是b的值卻沒有變,首先說明通過這種方式確實可以改變值街佑,但是為什么b的值沒有變化呢次酌?。請問為什么舆乔?我們再回過頭來看看外面的方法岳服,檢查一下,我們定義了:
有沒有發(fā)現(xiàn)什么問題希俩?
Integer是不是一個封裝類型吊宋,而他的值1,2,是不是一個int類型颜武,是一個基本數(shù)據(jù)類型璃搜,那么這里是怎么賦值的呢? 那么我們按照正常來寫是不是這樣子的:
int a = 1;
但是為什么使用Integer也不會報錯了鳞上,好这吻,這就講到了我們又一個知識點:(筆記)
Java中的裝箱和拆箱
裝箱:把基本類型用它們相應(yīng)的引用類型包裝起來,使其具有對象的性質(zhì)篙议。int包裝成Integer唾糯、float包裝成Float;
拆箱:和裝箱相反怠硼,將引用類型的對象簡化成值類型的數(shù)據(jù);
Integer a = 100; // 這是自動裝箱 (編譯器調(diào)用的是static Integer valueOf(int i))
int b = new Integer(100); //這是自動拆箱
那么我們來實際看下,我們耳聽為虛移怯,眼見為實香璃,我們來看下編譯的字節(jié)碼文件:
命令:javap -c Test.class
可以看到:
Jvm他自動做了裝箱操作,看的清清楚楚對吧舟误,對吧
好那么葡秒,我們來看下Integer.valueOf(1):源碼
意味著值大于IntegerCace.low小于IntegerCache.high的話:
會從IntegerCache中獲取,也就是從緩存中取值嵌溢。
那么我們來看下IntegerCache:
也就是說從-128到127直接的所有值眯牧,都是從緩存中獲取。而緩存中的值赖草,是什么時候放進去的学少,是jvm啟動的時候就放進去了,然后分配好內(nèi)存地址疚顷。
你們有沒有發(fā)現(xiàn)旱易,就短短幾行代碼禁偎,怎么就有這么多知識腿堤,是不是都有點感覺不認(rèn)識java了。很神奇吧如暖,哈哈好笆檀,前面這兩行代碼我們分析完了對吧,好盒至,然后酗洒,然后我們把ab的值傳進來,我們再來分析swap中的這段代碼枷遂,好吧樱衷,精華部分就是這段代碼了啊,這是精華部分酒唉,哈哈矩桂,我們來看:斷點到這句
然后按F5進去看下,把IntegerCache里面的值全面拿出來放到notepat++
第一步:是不是需要獲取num2的值痪伦,那么他從下標(biāo)[2+128=130]IntegerCache中獲取值為:130下標(biāo)侄榴,也就是第131個數(shù)字為:2
第二步:field.set(num1,num2),, 意味著第一步先獲取num1在IntegerCache中的值IntegerCache[1+128] =1 ,然后會修改IntegerCache[num1]的值為num2從Integercache中獲取到的值2网沾, 也就是修改為:integerCache[129] = 2
第三步:下一行代碼執(zhí)行
此時癞蚕,再次拿出IntegerCache, 那么下標(biāo)為129,130的值都變成了2辉哥, 此時tmp的值為1桦山,那么從IntegerCache獲取到的值為IntegerCache[1+128=129] ,也就是獲取130行的數(shù),也就是2,所以結(jié)果就是這樣度苔。實際上面和下面這個是一樣的:
從這一句debug進去:發(fā)現(xiàn)走的緩存匆篓,然后從cache中第129個下標(biāo)找到了。
所以寇窑,當(dāng)我們的值是在【-127-128】的時候鸦概,他是從IntegerCache中獲取的。其實甩骏,我們可以這樣來驗證一下:
結(jié)果為:true
結(jié)果為:false
那么窗市,這個當(dāng)時我其實又遇到這個坑,被坑慘了是吧饮笛。哈哈哈咨察。
那么我們怎么解決最后的問題:(最初的面試問題)
1.
2.
3.取巧的方式: