面试题
Integer i = new Integer(1);Integer j = new Integer(1);System.out.println(i == j); // falseInteger i = new Integer(1);int j = 1;System.out.println(i == j); // trueInteger m = 127;Integer n = 127;System.out.println(m == n); // trueInteger x = 128;Integer y = 128;System.out.println(x == y); //false
今天看到一道面试题, 运行结果如上, 第一个比较简单, 通过构造器 new 出了两个不同的包装类对象, 地址自然不同, 输出 false.
自动装箱与自动拆箱
在 JDK 5.0 之前, 创建基本数据类型对应的包装类对象需要手动装箱, 过程如下:
Integer m = new Integer(1);int i = Integer.intValue(m);
在 JDK 5.0 之后, 提供了自动装箱的方式来快速获取基本数据类型或其包装类对象:
// 整数类型的自动装箱与拆箱Integer m = 1;int i = m;// 浮点数类型的自动装箱与拆箱Double n = 1.0;double j = n;
通过反编译(Fernflower)查看代码:
Integer var1 = Integer.valueOf(1);int var2 = var1.intValue();Double var3 = Double.valueOf(1.0D);double var4 = var3.doubleValue();
可以发现, 整数的自动装箱实际上是调用了 Integer.valueOf() 方法, 拆箱实际上是调用了对象的 integer.intValue() 方法. 对于 Double, Character 等类型也是类似的.
总结:
- 自动装箱, 调用包装类的
.valueOf() - 自动茶香, 调用包装类对象的
.xxValue()
那么, 回到第二个问题, 在包装类对象和基本数据类型进行 == 判断时:
Integer n1 = 1;int n2 = 1;boolean bool1 = (n1 == n2);
通过反编译我们可以发现, 实际上是将包装类对象自动拆箱为基本数据类型来进行比较的:
Integer var1 = Integer.valueOf(1);byte var2 = 1;boolean var3 = var1.intValue() == var2;
Integer.valueOf()
我们接下来看 Integer 这个包装类的 valueOf() 方法:
/*** 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)];return new Integer(i);}
这个方法是根据 int i 的值来返回一个包装类实例.
有一点这里说的非常明白, “如果没有要求非要要一个新的包装类实例, 那么这个方法应该优先于构造器 new Integer() 使用, 因为这个方法缓存了经常使用的值, 所以很可能产生更好的时间和空间上的表现”, 这也是为什么在 JDK 9 之后就被注解为过期了的原因:
/** @deprecated */@Deprecated(since = "9")public Integer(int value) {this.value = value;}/** @deprecated */@Deprecated(since = "9")
那么我们平常在使用时也应该尽量多用自动装箱或 valueOf() 这个方法.
具体缓存哪些数呢?
Integer 类内有一个嵌套类 IntegerCache, 顾名思义即整数缓存, 里面将 low ~ high 的所有整数都预先缓存在了一个名为 cache 的 Integer 数组内.
这里, 最小值 low = -128 是固定的, 而最大值 high 则可以在 JVM 的 sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high") 这个属性设置, 默认的是 127, 之后便在这个嵌套类的静态代码块中生成了 -128 ~ 127 的整数包装类对象的数组.
再回到 valueOf() 方法体, 发现, 首先判断 int i 是否在 -128 ~ 127 之间, 如果在这之间, 那么直接返回 cache[] 数组内相应的实例, 否则, 才会调用构造函数 Integer(i), 返回一个新的包装类对象. 这有点像字符串常量池, 都是为了提高效率.
也就是说, 在 -128 ~ 127 这个范围内, 通过自动装箱(或 **valueOf()** 方法), 等价于获取 cache[] 数组内的相应整数的地址, 并不会生成新的对象! 而 在这个范围之外, 自动装箱(或 **valueOf()** 方法), 生成的都是新的包装类对象. 所以才了以下这个第三第四题对于 127 和 128 这两个仅仅相差一但匪夷所思的结果:
Integer m = 127;Integer n = 127;System.out.println(m == n); // trueInteger x = 128;Integer y = 128;System.out.println(x == y); // false
