暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

深入理解数值包装类型

四阿哥胤禛 2019-03-25
369

首先,我们来看一个例子,我们有 a, b两个Integer,希望实现两个值交换,预期结果第一次输出 a:1, b:2 , 第二次输出 a:2, b:1 。

    public class App {
    public static void main( String[] args ) {
    Integer a = 1;
    Integer b = 2;

    System.out.println("a: " + a + ", b: "+b);
    swap(a, b);
            System.out.println("a: " + a + ", b: "+b);
    }

    private static void swap(Integer a, Integer b) {
    try {
    int temp = a;
    Field field = Integer.class.getDeclaredField("value");
    field.setAccessible(true);
    field.set(a, b);
    field.set(b, temp);
    } catch (Exception e) {
    e.printStackTrace();
    }
    }
    }

    大家觉得上面的逻辑正确吗?我们不妨来运行一下


    当我们运行这个main方法时,我们会发现实际上第二次输出结果是 a:2, b:2  。

    小伙伴们惊呆了,为什么呢?底层到底做了什么,导致只交换了一个值呢?


    我们一步一步分析

      Integer a = 1;
      Integer b = 2;

      上面两步, Java对 数值 1, 2做了装箱操作,Integer.valueOf(xxx),然后分别赋值给a, b。那么装箱操作到底做了什么呢?


      我们来分析一下源码

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


        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() {}
        }

        从源码中不难看出,Integer缓存了 [-128, 127],并将区间的数值包装成了Integer ,当数值在这个区间时,根据数值计算索引值,然后直接从IntergerCache缓存中取, 若超过这个区间,则new一个Integer对象。


        了解完装箱操作的具体实现,接下来我们继续分析我们的代码

          Field field = Integer.class.getDeclaredField("value");
          field.set(a, b);

          这里,field.set(Object obj, Object value),通过反射将b赋值给a, 而b是 Integer.valueOf(2), a是Integer.valueOf(1),也就是将IntegerCache中下标130的值 赋给下标129。此时IntegerCache 129、130对应的值如下图


            field.set(b, temp);

            这段代码将temp赋值给b, temp值是 1, b值是2,执行filed.set(Object obj, Object value)方法时,第一步将数值类型进行装箱操作,temp变成了Integer.valueOf(1),下标129对应的值, b变成 Integer.valueOf(2) ,下标130对应的值,然后将temp的值赋给b;  我们通过上图可以看出 两者的值都是2,所以我们代码执行后,a和b的值都变成了2;


            那么了解了原理之后,是否有办法通过反射交换过来呢?

            Field提供了一个setInt(Object obj, int value)方法,可以实现。功能是实现了,但是有没有问题呢?


            我们来看一下下面修正后的代码,再加两行代码,看输出的结果是什么

              public class App {
              public static void main( String[] args ) {
              Integer a = 1;
              Integer b = 2;

              System.out.println("a: " + a + ", b: "+b);
              swap(a, b);
              System.out.println("a: " + a + ", b: "+b);

              Integer c = 1;
              System.out.println("c: " + c);
              }

              private static void swap(Integer a, Integer b) {
              try {
              int temp = a;
              Field field = Integer.class.getDeclaredField("value");
              field.setAccessible(true);
              field.set(a, b);
              field.setInt(b, temp);
              } catch (Exception e) {
              e.printStackTrace();
              }
              }
              }

              上面的代码执行结果,第一行 a:1, b:2 ; 第二行 a:2, b:1 ;  第三行 c:2


              我们发现 c 的值变了,根据之前讲的,我们知道 c 经过包装后,值是IntegerCache下标为129对应的值,IntegerCache经过swap修改后,下标129对应的值修改成了 2,下标130对应的值修改成了 1,导致 c 的值输出为2。因此,通过反射来实现数值类型的值交换是有问题的。



              文章转载自四阿哥胤禛,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

              评论