面试题
Integer i = new Integer(1);
Integer j = new Integer(1);
System.out.println(i == j); // false
Integer i = new Integer(1);
int j = 1;
System.out.println(i == j); // true
Integer m = 127;
Integer n = 127;
System.out.println(m == n); // true
Integer 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); // true
Integer x = 128;
Integer y = 128;
System.out.println(x == y); // false