簡介
最近有點忙,很久沒更新文章了,后面會慢慢恢復...回顧正題
最近看到一篇文章,關于一道面試題,先看一下題目,如下:
1
2
3
4
5
6
7
8
9
10
11
|
public static void main(string[] args) { integer a = 1 ; integer b = 2 ; system.out.printf( "a = %s, b = %s\n" , a, b); swap(a, b); system.out.printf( "a = %s, b = %s\n" , a, b); } public static void swap(integer a, integer b) { // todo 實現 } |
有人可能在沒經過仔細考慮的情況下,給出以下的答案
1
2
3
4
5
6
7
8
9
|
// 特別提醒,這是錯誤的方式 // 特別提醒,這是錯誤的方式 // 特別提醒,這是錯誤的方式 public static void swap(integer a, integer b) { // todo 實現 integer temp = a; a = b; b = temp; } |
很遺憾,這是錯誤的。重要的事注釋三遍
那么為什么錯誤,原因是什么?
想要搞清楚具體的原因,在這里你需要搞清楚以下幾個概念,如果這個概念搞清楚了,你也不會把上面的實現方法寫錯
- 形參和實參
- 參數值傳遞
- 自動裝箱
所以,上面的問題先放一邊,先看一下這幾個概念
形參和實參
什么是形參?什么是實參?概念上的東西,參考教科書或者google去吧,下面直接代碼說明更加明顯
1
2
3
4
5
6
7
8
9
|
public void test() { int shi_can = 0 ; testa(shi_can); } public void testa( int xing_can) { } |
注:為了清楚的表達意思,我命名的時候并沒有按照java的駝峰規則命名,這里只是為了演示
通過上面的代碼很清楚的表達形參和實參的概念,在調用testa時,傳遞的就是實參,而在testa方法簽名中的參數為形參
從作用域上看,形參只會在方法內部生效,方法結束后,形參也會被釋放掉,所以形參是不會影響方法外的
值傳遞和引用傳遞
值傳遞:傳遞的是實際值,像基本數據類型
引用傳遞:將對象的引用作為實參進行傳遞
java基本類型數據作為參數是值傳遞,對象類型是引用傳遞
實參是可以傳遞給形參的,但是形參卻不能影響實參,所以,當進行值傳遞的情況下,改變的是形參的值,并沒有改變實參,所以無論是引用傳遞還是值傳遞,只要更改的是形參本身,那么都無法影響到實參的。對于引用傳遞而言,不同的引用可以指向相同的地址,通過形參的引用地址,找到了實際對象分配的空間,然后進行更改就會對實參指向的對象產生影響
額,上面表述,可能有點繞,看代碼
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
|
// 僅僅是一個java對象 public class inttype { private int value; public int getvalue() { return value; } public void setvalue( int value) { this .value = value; } } // main方法 public class inttypeswap { public static void main(string[] args) { // code_1 inttype type1 = new inttype(); type1.setvalue( 1 ); inttype type2 = new inttype(); type2.setvalue( 2 ); // code_1 swap1(type1, type2); system.out.printf( "type1.value = %s, type2.value = %s" , type1.getvalue(), type2.getvalue()); swap2(type1, type2); system.out.println(); system.out.printf( "type1.value = %s, type2.value = %s" , type1.getvalue(), type2.getvalue()); } public static void swap2(inttype type1, inttype type2) { int temp = type1.getvalue(); type1.setvalue(type2.getvalue()); type2.setvalue(temp); } public static void swap1(inttype type1, inttype type2) { inttype type = type1; type1 = type2; type2 = type; } } |
在main方法中,code_1中間的代碼為聲明了兩個對象,分別設置value為1和2,而swap1和swap2兩個方法的目的是為了交互這兩個對象的value值
先思考一下,應該輸出的結果是什么
...
...
1
2
|
type1.value = 1 , type2.value = 2 type1.value = 2 , type2.value = 1 |
從輸出結果來看swap1并沒有達到目的,回頭看一下
1
2
3
4
5
|
swap1public static void swap1(inttype type1, inttype type2) { inttype type = type1; type1 = type2; type2 = type; } |
從值傳遞的角度來看,對象參數傳遞采用的是引用傳遞,那么type1和type2傳遞過來的是指向對象的引用,在方法內部,直接操作形參,交換了形參的內容,這樣形參改變,都是并沒有對實參產生任何影響,也沒有改變對象實際的值,所以,結果是無法交換
而對于swap2,對象引用作為形參傳遞過來后,并沒有對形參做任何的改變,而是直接操作了形參所指向的對象實際地址,那這樣,無論是實參還是其他地方,只要是指向該對象的所有的引用地址對應的值都會改變
自動裝箱
看我上面的那個例子的swap1,是不是頓時覺得與上面的面試題的錯誤做法非常相似了,是的,錯誤的原因是一模一樣的,就是稍微有一點區別,就是integer不是new出來的,而是自動裝箱的一個對象,那么什么是自動裝箱呢?jdk到底做了什么事?
如果你不想知道為什么,只想知道結果,那么我就直說,自動裝箱就是jdk調用了integer的valueof(int)
的方法,很簡單,看源碼
1
2
3
4
5
|
public static integer valueof( int i) { if (i >= integercache.low && i <= integercache.high) return integercache.cache[i + (-integercache.low)]; return new integer(i); } |
上面那些如果不想深究可以忽略,就看最后一句,是不是明白了什么呢。沒錯,也是new出來一個對象,如果想知道上面的代碼做了什么處理,可以參考 long==long有趣的現象 這篇文章,里面有介紹類似的
好了,有人可能會問,為什么會知道自動裝箱調用的是valueof方法,這里其他人怎么知道的我不清楚,我是通過查看反編譯的字節碼指令知道的
1
2
3
4
5
6
7
8
9
10
11
12
13
|
public static void main(string[] args) { integer a = 1 ; integer b = 2 ; system.out.printf( "a = %s, b = %s\n" , a, b); swap(a, b); system.out.printf( "a = %s, b = %s\n" , a, b); } public static void swap(integer a, integer b) { integer temp = a; a = b; b = temp; } |
反編譯出來的結果為
對比一下可以很清楚的看到valueof(int)
方法被調用
回歸
好,現在回歸正題了,直接操作形參無法改變實際值,而integer又沒有提供set方法,那是不是無解了呢?我很好奇如果有人以下這樣寫,面試官會有什么反應
1
2
3
4
|
public static void swap(integer a, integer b) { // todo 實現 // 無解, } |
既然出了肯定是有解的,可以實現,回頭看看,在上面swap2的那個例子中是通過set方法來改變值的,那么integer有沒有提供呢?答案沒有(我沒找到)
那就先看看源碼
1
2
3
4
5
|
private final int value; ... public integer( int value) { this .value = value; } |
這是integer的構造函數,可以看到integer對象實際值是用value屬性來存儲的,但是這個value是被final修飾的,沒辦法繼續找,value沒有提供任何的set方法。既然在萬法皆不通的情況下,那就只能動用反射來解決問題
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
public static void swap(integer a, integer b) { int temp = a.intvalue(); try { field value = integer. class .getdeclaredfield( "value" ); value.setaccessible( true ); value.set(a, b); value.set(b, temp); } catch (nosuchfieldexception e) { e.printstacktrace(); } catch (illegalaccessexception e) { e.printstacktrace(); } } |
現在感覺很開心,終于找到解決方案,可是當你執行的時候,從輸出結果你會發現,jdk在跟我開玩笑嗎
1
2
|
a = 1 , b = 2 a = 2 , b = 2 |
為什么會出現這種情況,無奈,調試會發現是在value.set
的時候將integer的緩存值改變了,因為value.set(object v1, object v2)
兩個參數都是對象類型,所以temp會進行自動裝箱操作,會調用valueof方法,這樣會獲取到錯誤的緩存值,所以,為了避免這種情況,就只能不需要調用緩存值,直接new integer
就可以跳過緩存,所以代碼改成如下即可
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
public static void swap(integer a, integer b) { int temp = a.intvalue(); try { field value = integer. class .getdeclaredfield( "value" ); value.setaccessible( true ); value.set(a, b); value.set(b, new integer(temp)); } catch (nosuchfieldexception e) { e.printstacktrace(); } catch (illegalaccessexception e) { e.printstacktrace(); } } |
至此,這道題完美結束
總結
以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,如果有疑問大家可以留言交流,謝謝大家對服務器之家的支持。
原文鏈接:https://juejin.im/post/5b8d21b651882542b20591af