今天在网上看到一篇文章,如何编程实现 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() {}
- }
注意点:
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);
- }
如图所示

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个不同类型的对象。
注意点:编译器不允许不同包装类型的对象使用 == 符号。
- @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 指向的地址也不会变。
