关于Integer一些隐藏关卡

反射绕过安全保护修改integer时候的典型错误,使用反射强行修改的时候请慎重

Integer在方法中没有提供value的get和set方法,如果现在需要你编写一个函数,使得交换俩个值,你会发现java在这里的值传递在Integer里面是copy了一个副本指向值,而不是直接地址。

那如果就是需要拥有这么一个方法的话,那么应该怎么做呢:

private static void swap(Integer a, Integer b) throws NoSuchFieldException, IllegalAccessException {
        //利用反射修改
        Field field = Integer.class.getDeclaredField("value");
        //绕过安全检查修改final值
        field.setAccessible(true);
        //暂存a的值
        int temp = a.intValue();
        交换
        field.set(a, b);  
        field.set(b, temp)    //参数需要俩个object 所以这里会装箱
    }
}

这段代码不难理解,其实就是:

int temp = a;
a = b;
b = temp 

只不过这里是使用的反射的机制,本质还是不变的;

既然方法编写成功啦,那么我们编写一个main函数开始测试吧!

public static void main(String[] args) throws NoSuchFieldException,             IllegalAccessException {
        Integer a = 1;
        Integer b = 2;
        System.out.println("a = " + a + ", b = " + b);
        swap(a, b);
        System.out.println("a = " + a + ", b = " + b);
}

在大家心里肯定是已经有答案了吧,不过现在的结果可能会大跌眼镜:

a = 1, b = 2
a = 2, b = 2

诶!这里为啥俩个值都会变成2呢,其实这里也是让我自己都感觉到不可思议

先让我们知道装箱的概念

如果不理解的可以看看我的另外一篇文章Java装箱和拆箱

现在我们再次看看valueOf这个函数

public static Integer valueOf(int i) {
      if (i >= IntegerCache.low && i <= IntegerCache.high)
          return IntegerCache.cache[i + (-IntegerCache.low)];
      return new Integer(i);
  }

其实这里就是准备-128 到 127 缓冲区来应对高频率的整数使用

field.set(a, b);  

问题就是出现在这一句话里面,

这里的a和b需要看main函数的初始化,不难发现他们就是由自动装箱来的,那么他们俩个其实就是

 a = IntegerCache.cache[1 + -(-128)]  //cache[129]
 b = IntegerCache.cache[2 + -(-128)]     //cache[130]

那么重点来啦,现在field使用反射强行修改值,这样的操作一直都是不安全的操作,这里就是一个非常典型的例子, 这句话直接把缓冲区修改了!

cache[129] = b.intValue()  // cache[129] = 2;

所以接下来的

field.set(b, temp)

这里set方法参数需要俩个object 所以这里会装箱, 也就是 temp装箱执行代码

IntegerCache.cache[temp + -(-128)]  // temp = 1

结果已经显而易见啦!这个时候的temp装箱以后居然变成了2!所以导致了错误

看到这里已经明了了把,其实就是一句话,

使用反射修改了缓冲区,使得缓冲区完全错乱

那在进行装箱就不会有正确的结果啦,这里为了让大家更加能够理解和看到错误的缓冲区,我们再来一段代码

main函数声明

  Integer a = 1;
  Integer l = 1;
  Integer m = 1;
  Integer b = 2;

方法中

  field.set(a, b);
  field.set(b, new Integer(10));
  field.set(3, new Integer(11));
  field.set(5, new Integer(100));

输出缓冲区0 到 10 的元素

for (int i = 0; i <= 10; i++){
  if(i % 10 == 0){
      System.out.println("");
  }
  System.out.print(i + " " + Integer.valueOf(i) + " , ");
}

答案

0 0 , 1 2 , 2 10 , 3 11 , 4 4 , 5 100 , 6 6 , 7 7 , 8 8 , 9 9 

现在很清楚了把,缓冲区被轻而易举的修改啦,所以大家要牢记不要滥用反射去修改那些java保护起来的东西,java做好的保护不让你碰其实就是怕你掉进坑了,所以使用反射的时候千万要思考周全以后再来使用

博文是作者原本在其他平台的,现迁移过来


mackyHuang
5 声望0 粉丝

爱技术