先看这道题目.
1 2 3 4 5 6 7 8 9 10 11 12 13 public class MyClass { public static void main (String[] args) { Integer a = 10 , b = 20 ; System.out.println("before a=" + a + ",b=" + b); swap(a, b); System.out.println("after a=" + a + ",b=" + b); } private static void swap (Integer i1, Integer i2) { } }
方法传递的是引用,Integer是java中对于int基本类型的封装类.这个类貌似并没有setXXX这样的方法来改变内部的某个值.
好吧.阅读Integer源码得知,其内部有个私有的value变量.于是反射修改之.
1 2 3 4 5 6 7 8 9 10 11 12 private static void swap (Integer i1, Integer i2) { try { Field f = Integer.class.getDeclaredField("value" ); f.setAccessible(true ); f.set(i1, 20 ); f.set(i2, 10 ); } catch (NoSuchFieldException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } }
运行一下:
before a=10,b=20 after a=20,b=20
a修改成功了.b却没修改成功.百思不得其解啊.
这里发现两个问题:
1.反射时,set参数接收的是Object类型,此处传入int,会将基本类型转换为包装类型即Integer.(很重要)
2.第二个参数在调用处传的是10,此处变成了一个intvalue为20的Integer对象.(???)
这里有一个知识点—-Integer中的IntegerCache:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 private static class IntegerCache { static final int low = -128 ; static final int high; static final Integer cache[]; static { int h = 127 ; String integerCacheHighPropValue = sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high" ); if (integerCacheHighPropValue != null ) { int i = parseInt(integerCacheHighPropValue); i = Math.max(i, 127 ); h = Math.min(i, Integer.MAX_VALUE - (-low) -1 ); } high = h; cache = new Integer[(high - low) + 1 ]; int j = low; for (int k = 0 ; k < cache.length; k++) cache[k] = new Integer(j++); } private IntegerCache () {} }
简单来说,为了防止在int类型自动装箱拆箱过程中频繁的new Integer(),Interger内部使用IntegerCache类建立了一个默认255长度的缓存池.初始化时就会生成-128~127之间对应的所有Integer对象.
什么时候使用这个缓存呢?调用Integer.valueOf()方法的时候.
什么时候调用这个方法?Integer i = 10;这样写,系统自动装箱的时候.
而真正的new Integer()方法则才是创建一个新的对象,并将内部的value值赋值为构造方法传入的值.
1 2 3 public Integer (int value) { this .value = value; }
那么现在重新分析一下前面的代码.
//从缓存中找到指定位置的两个Integer对象,a,b分别指向这两个对象.
//为方便后面描述,暂忽略负数部分.假定a指向第10个位置,b指向第20个位置.
1 2 3 Field f = Integer.class.getDeclaredField("value" ); f.setAccessible(true ); f.set(i1, 20 );
//此处将第10个位置的Integer对象内部的value设置为20.(WTF…这里会造成很大的问题…)
//这里首先会将10进行自动装箱.实际上就是调用Integer.valueOf(10),进而从IntegerCache中找第10个对象.
//but,第10个对象内部的value还是10么?被上一步改成20了…
//于是最终就会出现反射debug看到的var1=”20”,var2=”20”了…
修改结果:失败.
在揭露正确方法前,先做一个小测验来验证一下前面的结果.
将f.set(i2, 10);改为f.set(i2, 11);
before a=10,b=20 after a=20,b=11
果然…
那么现在问题就在于不能使用IntegerCache中的对象,因为那个对象已经乱掉了…
使用f.set(i2, new Integer(10));搞定!
这个题目其实很坑,因为这样的解法只是为了考验对Integer内部缓存的理解.实际情况中使用这个绝对会得不偿失,带来莫名其妙的问题.
比如,后面跟着这样写:
1 2 Integer c = 10 ; System.out.println(c);
可以看看打印结果…wtf