太菜了,没能写出a == 1 && a == 2 && a == 3 为True的代码

230 阅读3分钟

乍眼一看,能让a == 1 && a == 2 && a == 3这似乎是不太可能的事,但是能这么问,肯定是有办法实现的。

这也是今天刚看到的一个帖子,所以也比较好奇,但是帖子中并没有说明原理,所以在此尝试解释一下这段神奇的代码。

public class Main {
   public static void main(String[] args) {
       try {
           Class cache = Integer.class.getDeclaredClasses()[0];
           Field c = cache.getDeclaredField("cache");
           c.setAccessible(true);
           Integer[] array = new Integer[0];
           array = (Integer[]) c.get(cache);
           array[130] = array[129];
           array[131] = array[129];
           Integer a = 1;
           if(a == (Integer)1 && a == (Integer)2 && a == (Integer)3){
               System.out.println("相等");
           }
       } catch (IllegalAccessException e) {
           e.printStackTrace();
       } catch (NoSuchFieldException e) {
           e.printStackTrace();
       }
   }
}

其实上面这段代码并没有真正意义上的让a == 1 && a == 2 && a == 3,相信也不可能有这样的值,只不过各类语言可以使用自己的特性用障眼法技巧来让结果为True。

在Java中,要了解这段代码,需要有两个知识点,反射和Integer缓存。

反射先不说了,重要的是Integer的缓存,在 Java 5 中,引入了一项新功能用来节省Integer类型对象的内存,就叫Integer缓存,其实就是事先创建了指定范围内整数的Integer类型,默认情况下会保存–128 到 +127之间的整数值,这个最大值可以通过参数设置-XX:AutoBoxCacheMax=NEWVALUE或-Djava.lang.Integer.IntegerCache.high=NEWVALUE。

这也就是为什么new Integer(10)==new Integer(10)结果False,而Integer.valueOf(10)== Integer.valueOf(10)为True的原因,因为在Integer.valueOf方法下,会判断目标值是不是已经被创建过了,是的话就直接返回,不再就重新new,所以每次地址也不一样。

而这个缓存是由一个Integer cache[]保存的,所以,是不是可以修改其中几个值,让他保存的内容都和1的地址相等,那么在valueOf的时候,即使两个值不一样,但是返回的地址还是一样的?

这就需要用到反射了,Integer的缓存保存在内部类IntegerCache中,所以第一步需要通过以下代码获取。

  Class cache = Integer.class.getDeclaredClasses()[0];
  Field c = cache.getDeclaredField("cache");

getDeclaredClasses方法会返回所有内部类,结果是一个数组(Integer中只有一个内部类)。

然后获取静态字段cache,对他设置允许访问并读取值。

  c.setAccessible(true);
  Integer[] array = new Integer[0];
  array = (Integer[]) c.get(cache);
  array[130] = array[129];
  array[131] = array[129];
  Integer a = 1;

此时array数组默认情况下保存着-127到+128中共256个数,下标129存储的是1,把他的地址都赋给下标130、131。

那么在通过valueOf(1)、valueOf(2)、valueOf(3)比较的时候,他们三个地址都是一样的,所以结果为True。

最重要的是,要把他们都强制转换成Integer对象才能比较,否则,虚拟机会调用a.intValue进行比较,结果是不可能为True的,强制转换的时候,会调用valueOf方法。

Integer a = 1;
if(a == (Integer)1 && a == (Integer)2 && a == (Integer)3){
    System.out.println("相等");
}