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)避免了裝箱。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章