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