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的引用和值傳遞的知識。java

3. Java中都是值傳遞

也就是說,函數的參數變量都是對原來值的copy,這也是java和c的一個明顯區別。舉個例子。緩存

1處和2處兩個引用的指向都是同一塊內存,可是count == countCopy答案是false。函數

你在家看電視,用遙控器正在更換頻道,這時候你爸跟你說「把遙控器給我!剛纔那個節目很好看」。此時,你爲了避免丟失對電視的控制權,你從抽屜裏拿了一個新的遙控器給了你爸(複製一個新的)。新、舊兩個遙控器就如同上面的count,countCopy。spa

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。code

這裏要補充一個細節,你能夠在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));
}

程序輸出get

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。因此說盡可能不要用反射去改變類的私有變量。

相關文章
相關標籤/搜索