Java交換兩個Integer-一道無聊的題的思考
1.最近網上看到的一道題,有人說一道很無聊的題,但我覺得有必要記錄一下。
2.題目
public static void main(String[] args) throws Exception { Integer a = 3; Integer b = 5; System.out.println("before swap: a="+ a + ",b=" + b); swap(a,b); System.out.println("after swap: a="+ a + ",b=" + b); } public static void swap(Integer a,Integer b) throws Exception { //TODO 請實現邏輯 }
看了題目之後,首先想到的是 加減法,異或操作交換等。但仔細思考之後,發現考察點並不是這個。至少,你先要了解java的引用和值傳遞的知識。
3. Java中都是值傳遞
也就是說,函式的引數變數都是對原來值的copy,這也是java和c的一個明顯區別。舉個例子。
1處和2處兩個引用的指向都是同一塊記憶體,但是count == countCopy答案是false。
你在家看電視,用遙控器正在更換頻道,這時候你爸跟你說“把遙控器給我!剛才那個節目很好看”。此時,你為了不丟失對電視的控制權,你從抽屜裡拿了一個新的遙控器給了你爸(複製一個新的)。新、舊兩個遙控器就如同上面的count,countCopy。
public static void main(String[] args) throws Exception {
Integer count = new Integer(100);//1
test(count);
}
public static void test(Integer countCopy){//2
System.out.println(countCopy);
}
4.回到題目
如果給出的不是引用型別Integer而是int交換,這題是無解的。因為swap函式裡的a,b都是引用的copy。所以你改變swap中a,b的引用指向是沒用的,因為無法影響到主函式中的引用a,b的指向。所以思路還是隻能從更改引用指向的真實記憶體值來解決(要拆開電視,更換零件;只拿著遙控器一噸操作是沒法讓電視機硬體產生變化的),所以自然要用到反射了。最初的我解答如下(下面這份程式碼是有問題的)
public static void swap(Integer a,Integer b) throws Exception {
Field valueField = Integer.class.getDeclaredField("value");
valueField.setAccessible(true);
int tmpA = a.intValue();//3
int tmpB = b.intValue();//5
valueField.set(a,tmpB);
valueField.set(b,tmpA);
}
//程式輸出結果
before swap: a=3,b=5
3========>5
5========>5
after swap: a=5,b=5
發生了什麼?為什麼交換後b=5而不是3?別急我們根據上面的程式碼,進行DEBUG。
這裡要補充一個細節,你可以在valueOf函式裡面打個斷點,發現的確會進去。
Integer a = 3;
//等價與
Integer a = Integer.valueOf(3);
那麼上面有問題的程式碼 valueField.set(b,tmpA); 因為tmpA是int型別,在賦值的時候也會隱式呼叫Integer.valueOf封裝成物件,然後再進行set賦值。懷疑問題就是在set這個方法了嗎?但是 valueField.set(a,tmpB);是有效的,valueField.set(b,tmpA)是無效的。稍微改動一下程式,進一步探索。
public static void swap(Integer a,Integer b) throws Exception {
Field valueField = Integer.class.getDeclaredField("value");
valueField.setAccessible(true);
int tmpA = a.intValue();//3
int tmpB = b.intValue();//5
System.out.println(Integer.valueOf(3)+"======" + Integer.valueOf(5));
valueField.set(a,tmpB);
System.out.println(Integer.valueOf(3)+"======" + Integer.valueOf(5));
valueField.set(b,tmpA);
System.out.println(Integer.valueOf(3)+"======" + Integer.valueOf(5));
}
程式輸出
before swap: a=3,b=5
3======5
5======5
5======5
after swap: a=5,b=5
可以發現在第一個進行反射賦值valueField.set(a,tmpB);後,Integer.valueOf(3) 等於 5 ???
進去看看valueOf的原始碼:
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
IntegerCache是個什麼鬼?而且IntegerCache.low = -128, IntegerCache.high = 127。valueOf(3)肯定會命中快取,那麼通過Debug除錯,發現IntegerCache的確出錯了,cache[3] = 5 (其實真實3的快取下標並不是3,而是i + (-IntegerCache.low),這裡便於說明理解)。
經過這些分析,問題表現在 valueField.set(a,tmpB); 賦值後
命中IntegerCache,獲取cache(5)即5,並更新快取cache(3)=5
那麼如果解決呢,其實只要避開呼叫valueOf即可,也就是通過new Integer()來繞開快取。修改後的程式碼如下:
public static void swap(Integer a,Integer b) throws Exception {
Field valueField = Integer.class.getDeclaredField("value");
valueField.setAccessible(true);
int tmpA = a.intValue();//3
int tmpB = b.intValue();//5
System.out.println(Integer.valueOf(3)+"======" + Integer.valueOf(5));
valueField.set(a,new Integer(tmpB));
System.out.println(Integer.valueOf(3)+"======" + Integer.valueOf(5));
valueField.set(b,new Integer(tmpA));
System.out.println(Integer.valueOf(3)+"======" + Integer.valueOf(5));
}
//輸出
before swap: a=3,b=5
3======5
5======5
5======3
after swap: a=5,b=3
但是 Integer.valueOf(3)的值還是5,如果程式的其他地方也用到了Integer.value(3)那麼將造成致命bug。所以說盡量不要用反射去改變類的私有變