首先,我们来看一个例子,我们有 a, b两个Integer,希望实现两个值交换,预期结果第一次输出 a:1, b:2 , 第二次输出 a:2, b:1 。
public class App {public static void main( String[] args ) {Integer a = 1;Integer b = 2;System.out.println("a: " + a + ", b: "+b);swap(a, b);System.out.println("a: " + a + ", b: "+b);}private static void swap(Integer a, Integer b) {try {int temp = a;Field field = Integer.class.getDeclaredField("value");field.setAccessible(true);field.set(a, b);field.set(b, temp);} catch (Exception e) {e.printStackTrace();}}}
大家觉得上面的逻辑正确吗?我们不妨来运行一下
当我们运行这个main方法时,我们会发现实际上第二次输出结果是 a:2, b:2 。
小伙伴们惊呆了,为什么呢?底层到底做了什么,导致只交换了一个值呢?
我们一步一步分析
Integer a = 1;Integer b = 2;
上面两步, Java对 数值 1, 2做了装箱操作,Integer.valueOf(xxx),然后分别赋值给a, b。那么装箱操作到底做了什么呢?
我们来分析一下源码
public static Integer valueOf(int i) {assert IntegerCache.high >= 127;if (i >= IntegerCache.low && i <= IntegerCache.high)return IntegerCache.cache[i + (-IntegerCache.low)];return new Integer(i);}private static class IntegerCache {static final int low = -128;static final int high;static final Integer cache[];static {// high value may be configured by propertyint 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);// Maximum array size is Integer.MAX_VALUEh = 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() {}}
从源码中不难看出,Integer缓存了 [-128, 127],并将区间的数值包装成了Integer ,当数值在这个区间时,根据数值计算索引值,然后直接从IntergerCache缓存中取, 若超过这个区间,则new一个Integer对象。
了解完装箱操作的具体实现,接下来我们继续分析我们的代码
Field field = Integer.class.getDeclaredField("value");field.set(a, b);
这里,field.set(Object obj, Object value),通过反射将b赋值给a, 而b是 Integer.valueOf(2), a是Integer.valueOf(1),也就是将IntegerCache中下标130的值 赋给下标129。此时IntegerCache 129、130对应的值如下图

field.set(b, temp);
这段代码将temp赋值给b, temp值是 1, b值是2,执行filed.set(Object obj, Object value)方法时,第一步将数值类型进行装箱操作,temp变成了Integer.valueOf(1),下标129对应的值, b变成 Integer.valueOf(2) ,下标130对应的值,然后将temp的值赋给b; 我们通过上图可以看出 两者的值都是2,所以我们代码执行后,a和b的值都变成了2;
那么了解了原理之后,是否有办法通过反射交换过来呢?
Field提供了一个setInt(Object obj, int value)方法,可以实现。功能是实现了,但是有没有问题呢?
我们来看一下下面修正后的代码,再加两行代码,看输出的结果是什么
public class App {public static void main( String[] args ) {Integer a = 1;Integer b = 2;System.out.println("a: " + a + ", b: "+b);swap(a, b);System.out.println("a: " + a + ", b: "+b);Integer c = 1;System.out.println("c: " + c);}private static void swap(Integer a, Integer b) {try {int temp = a;Field field = Integer.class.getDeclaredField("value");field.setAccessible(true);field.set(a, b);field.setInt(b, temp);} catch (Exception e) {e.printStackTrace();}}}
上面的代码执行结果,第一行 a:1, b:2 ; 第二行 a:2, b:1 ; 第三行 c:2
我们发现 c 的值变了,根据之前讲的,我们知道 c 经过包装后,值是IntegerCache下标为129对应的值,IntegerCache经过swap修改后,下标129对应的值修改成了 2,下标130对应的值修改成了 1,导致 c 的值输出为2。因此,通过反射来实现数值类型的值交换是有问题的。




