由一道算法题引出的基本类型封装类问题

先看这道题目.

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 {
// high value may be configured by property
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);
// Maximum array size is Integer.MAX_VALUE
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;
}

那么现在重新分析一下前面的代码.

1
Integer a = 10, b = 20;

//从缓存中找到指定位置的两个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…这里会造成很大的问题…)

1
f.set(i2,10);

//这里首先会将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