1. 程式人生 > >Java交換兩個Integer-一道無聊的題的思考

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