今天在网上看到一篇文章,如何编程实现 2 + 2 = 5? - 编程 - 软件编程 - 深度开源
以前没看过类似的题目,引起了我的好奇心,于是仔细看了看文章,知道原来是利用了Java包装类型的实例池(也就是缓存数组)特性才实现 2+2=5。
一、缓存数组
1.1、在Java中,每个基本类型(byte、char、 short、int、long、float、double)都有对应的包装类型。
例如 int 对应的包装类型为 Integer。
查看 Integer 源码可知 Java 在Integer内部定义了一个静态内部类 IntegerCache,里面声明了一个 Integer类型的 cache缓存数组,
JVM启动程序时,加载 Integer 类到内存后,先通过静态代码块,从位置0开始按照顺序创建 Integer 对象 (value范围 [-128,127]) 赋值给cache缓存数组。
private static class IntegerCache {
static final int low = -128;
static final int high;
static final Integer cache[];
static {
...//省略其他代码
cache = new Integer[(high - low) + 1];
int j = low;
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);
...
}
private IntegerCache() {}
}
注意点:
- byte、short、int、long 的包装类型都有范围 [-128,127] 的缓存数组。
- char 的包装类型的缓存数组值范围是 [0,127]。
- float 和double 的包装类型没有缓存数组。
1.2、在需要创建Integer对象时,先判断赋值给Integer的 int 值是不是在cache缓存数组的范围 [-128,127] 内。如果是,则不创建Integer对象,而是根据 int 计算找到对应cache缓存数组的Integer对象的地址,返回对象地址。如果不在范围内,则创建一个新的Integer对象并返回对象地址,例如 Integer的valueOf(int i) 方法。
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
//先判断 i 是否在缓存数组的范围内
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
二、面试题
2.1、判断值相同的2个 Integer 对象是否相等
如图所示
Debug模式下,从b1、i1、i2、i3、i4的引用地址可看出,
i1和i2指向同一个Integer对象,因为i1和i2的值都为0,在Integer的cache缓存数组 [-128,127] 范围内,所以在自动装箱成Integer对象时,不创建对象,而是返回缓存数组中存储的对应的对象地址,所以 i1 == i2 为 true。
而i3和i4的值为129,不在 cache缓存数组 [-128,127] 范围内,所以会创建新的Integer对象并返回对象地址,所以i3和i4分别指向不同对象,引用地址不一样,所以 i3 == i4 为false。
b1虽然值也为0,但是它对应的是 Byte 包装类型的cache缓存数组的对象,与i1和i2对应的对象是2个不同类型的对象。
注意点:编译器不允许不同包装类型的对象使用 == 符号。
2.2、编程实现2+2=5
@Test
public void test21() throws NoSuchFieldException, IllegalAccessException {
//通过反射获取Integer的内部类
Class<?>[] declaredClasses = Integer.class.getDeclaredClasses();
//因为Integer只有一个IntegerCache静态内部类,所以可直接通过索引=0得到IntegerCache 类
Class<?> cacheClass = declaredClasses[0];
//得到IntegerCache的cache缓存数组
Field cache = cacheClass.getDeclaredField("cache");
cache.setAccessible(true);
Integer[] o = (Integer[]) cache.get(null);
for (Integer integer : o) {
//从 -128 到 127
//System.out.println(integer);
}
//o[132]原本存储的是值为4,o[133]原本存储的是值为5
o[132] = o[133];
Integer i = 2+2;
System.out.println(i);
}
之所以 2+2 赋值给Integer对象i后, 输出为 5,是因为在Integer中有一个范围为 [-128,127] 的cache缓存数组,当需要创建Integer对象时,判断赋值给Integer对象的值是否在 [-128,127] 范围内,如果在,则不会创建新的Integer对象,而是返回cache缓存数组的对应对象的地址,而 Integer i = 2+2,2+2 计算得到 4 后,自动装箱创建Integer对象时,由于 4 在 [-128,127] 的缓存范围内,所以不会创建新的Integer对象,而是返回缓存数组 o[132](索引位置计算:4+(-)(-128)) 存储的对象地址,所以 i 和 o[132] 指向同一个对象。cache缓存数组是从0开始存储 [-128,127] 的值,o[132] 原本指向值为 4 的Integer对象,后面通过反射将 o[133] 的地址赋值给 o[132],而 o[133] 指向的是值为 5 的Integer对象,o[132] 实际指向值为 5 的Integer对象,o[132] 赋值给 i,i 也指向了值为 5 的Integer对象,打印 i 时,会将指向的对象的value值对应出来,也就是打印 5,所以得到 2+2=5。
注意点:
1、实现 2+2=5 需要通过包装类型对象才能实现,因为需要借助包装类型的缓存数组,普通的 2+2 还是为4。
2、如果是将 o[132] = o[133] 和 Integer i = 2+2 代码调换位置,而输出 i 还是会得到正确的结果 4,因为调换位置后,o[132]此时指向的是值为 4 的正确Integer对象,赋值给 i,所以 i 指向的值为4 的Integer对象,后面 o[133] 再赋值给 o[132],i 指向的地址也不会变。