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。因此说尽可能不要用反射去改变类的私有变量。

相关文章
相关标签/搜索