Integer:颠覆你的认知

  • Integer:颠覆你的认知

    本文涉及的知识点:值传递和引用传递自动装箱和拆箱Integer缓存

    一、装箱拆箱 & Integer缓存

    一道面试题

      public static void main(String[] args) {
            Integer a = 127;
            Integer b = 127;
            Integer c = new Integer(127);
            Integer d = 128;
            Integer e = 128;
            int f          = 128;
    
            System.out.println(a == b);//true
            System.out.println(a == c);//false
            System.out.println(d == e);//false
            System.out.println(e == f);//true
        }
    

    答案质疑

    这里Integer a = 127会自动装箱,其实就是Integer a = Integer.valueOf(127)。那么你一定会问,既然a和b都是对象,a==b只有引用地址一样才相等,而a和b是两个对象,应该不等才对。而同样的d和e确不相等,这里面有什么蹊跷?下面请听我娓娓道来。

    Integer.valueOf(int i)的玄机

     /**
         * Returns an {@code Integer} instance representing the specified
         * {@code int} value.  If a new {@code Integer} instance is not
         * required, this method should generally be used in preference to
         * the constructor {@link #Integer(int)}, as this method is likely
         * to yield significantly better space and time performance by
         * caching frequently requested values.
         *
         * This method will always cache values in the range -128 to 127,
         * inclusive, and may cache other values outside of this range.
         *
         * @param  i an {@code int} value.
         * @return an {@code Integer} instance representing {@code i}.
         * @since  1.5
         */
        public static Integer valueOf(int i) {
            if (i >= IntegerCache.low && i <= IntegerCache.high)
                //传入的值在给定的一个区间内会去缓存拿
                return IntegerCache.cache[i + (-IntegerCache.low)];
            //不在给定的区间,则new一个    
            return new Integer(i);
        }
    

    所以这就解答上面的疑惑,127在给定的范围内,从缓存拿去,a和b指向同一个对象。128不在指定的范围内,所以每次都是new一个Integer对象。下面看看IntegeCache是什么鬼?

    IntegerCache

    它是Integer的一个内部类

    /**
         * Cache to support the object identity semantics of autoboxing for values between
         * -128 and 127 (inclusive) as required by JLS.
         *
         * The cache is initialized on first usage.  The size of the cache
         * may be controlled by the {@code -XX:AutoBoxCacheMax=<size>} option.
         * During VM initialization, java.lang.Integer.IntegerCache.high property
         * may be set and saved in the private system properties in the
         * sun.misc.VM class.
         */
    
        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) {
                    try {
                        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);
                    } catch( NumberFormatException nfe) {
                        // If the property cannot be parsed into an int, ignore it.
                    }
                }
                high = h;
    
                cache = new Integer[(high - low) + 1];
                int j = low;
                for(int k = 0; k < cache.length; k++)
                    cache[k] = new Integer(j++);
    
                // range [-128, 127] must be interned (JLS7 5.1.7)
                assert IntegerCache.high >= 127;
            }
    
            private IntegerCache() {}
        }
    

    可以看到前面说的缓存命中区间的最小值就是-128,最大值默认是127,可以去指定最大值。它就是个Integer数组,cache[0] = -128.....cache[256]=127。

    二、值传递和引用传递

    一道面试题

    public static void main(String[] args) {
            Integer a = 1;
            Integer b = 2;
            System.out.println("before:a=" + a + ",b=" + b); 
            //编写代码,交换a和b的值
            System.out.println("after:a=" + a + ",b=" + b);
        }
    

    解法一

        public static void main(String[] args)  {
            Integer a = 1;
            Integer b = 3;
            System.out.println("before:a=" + a + ",b=" + b);
            Integer temp = a;
            a = b;
            b = temp;
            System.out.println("after:a=" + a + ",b=" + b);
        }
    

    输出:

    before:a=1,b=3
    after:a=3,b=1

    解法二(错误)

     public static void main(String[] args) {
            Integer a = 1;
            Integer b = 3;
            System.out.println("before:a=" + a + ",b=" + b);
            swap(a, b);
            System.out.println("after:a=" + a + ",b=" + b);
        }
    
        private static void swap(Integer a, Integer b) {
            Integer temp = a;
            a = b;
            b = temp;
        }
    

    输出:

    before:a=1,b=3
    after:a=1,b=3

    发现a和b的值并没有变,和解法一思路一样,只是将代码抽取出来了,这是为什么呢?

    因为swap方法是引用传递,传进来的是a和b在堆中的内存地址,对a和b的交换并不会改变原来的值。

    举个其他的demo:

    public class Apple {
        private String id;
        private int weight;
    
        public String getId() {
            return id;
        }
        public void setId(String id) {
            this.id = id;
        }
        public void setWeight(int weight) {
            this.weight = weight;
        }
    }
    
     public static void main(String[] args) {
            Apple apple = new Apple();
            apple.setWeight(100);
            save(apple);
            System.out.println(apple.getId());
        }
    
        public static void save(Apple apple) {
            apple.setId("uuid");
            apple = new Apple();
            apple.setId("id");
        }
    

    输出:

    uuid

    发现save方法里new Apple()后setId并不会改变传进来的引用的id值,apple = new Apple()指向另一个内存地址了,不会改变原来内存地址里的值。

    解法三

    因为Integer的value没有对外set方法,这里用反射改变value的值。

    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
            Integer a = 1;
            Integer b = 3;
            System.out.println("before:a=" + a + ",b=" + b);
            swap(a, b);
            System.out.println("after:a=" + a + ",b=" + b);
        }
    
        private static void swap(Integer a, Integer b) throws NoSuchFieldException, IllegalAccessException {
    
            Field field = Integer.class.getDeclaredField("value");
            field.setAccessible(true);
    
            int temp = a.intValue();
            field.set(a, b.intValue());
            field.set(b, temp);
    
            System.out.println(temp);
            System.out.println(Integer.valueOf(temp));
        }
    

    输出:

    before:a=1,b=3
    1
    3
    after:a=3,b=3

    发现a的值为b的值了,但是b没有变为a的值。而且蹊跷的是temp的值是1了,按道理 field.set(b, 1)应该是能把b设置为1的啊,而且Integer.valueOf(1)打印结果却是3???这里到底发生了什么?

    可以猜到 field.set(b, temp);里temp会有个自动装箱Integer.valueOf(temp),因为public void set(Object obj, Object value)
    vaulue是一个对象,而temp是int,所以会有一个自动装箱的隐式操作。那么int temp = 1,为啥装箱后变成3了呢?

    Integer.valueOf(temp = 1):

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

    原因就在这个缓存里,因为1会命中缓存,得到结果是3。

    缓存中数组里的值应该是连续的,但是却出现了断层,有两个3,这是因为 field.set(a, b.intValue())将Integer@502的value设置成了3。Integer.valueOf(1)实际就是从缓存中拿到对象Integer@502,值还是3。

    如何解决这个问题,也就是避免自动装箱?有两个措施:

    • field.setInt((Object obj, int value),这就不会装箱,直接设置成传入的值。
    • Integer temp = new Integer(a.intValue());也就是new Integer(1),就不会使用IntegerCache里的Integer对象,field.set(b, temp)避免了装箱。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章