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