-
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:颠覆你的认知
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.