面试题

  1. Integer i = new Integer(1);
  2. Integer j = new Integer(1);
  3. System.out.println(i == j); // false
  4. Integer i = new Integer(1);
  5. int j = 1;
  6. System.out.println(i == j); // true
  7. Integer m = 127;
  8. Integer n = 127;
  9. System.out.println(m == n); // true
  10. Integer x = 128;
  11. Integer y = 128;
  12. System.out.println(x == y); //false

今天看到一道面试题, 运行结果如上, 第一个比较简单, 通过构造器 new 出了两个不同的包装类对象, 地址自然不同, 输出 false.

后面三个需要进一步了解, 首先是自动装箱和拆箱的问题:

自动装箱与自动拆箱

在 JDK 5.0 之前, 创建基本数据类型对应的包装类对象需要手动装箱, 过程如下:

  1. Integer m = new Integer(1);
  2. int i = Integer.intValue(m);

在 JDK 5.0 之后, 提供了自动装箱的方式来快速获取基本数据类型或其包装类对象:

  1. // 整数类型的自动装箱与拆箱
  2. Integer m = 1;
  3. int i = m;
  4. // 浮点数类型的自动装箱与拆箱
  5. Double n = 1.0;
  6. double j = n;

通过反编译(Fernflower)查看代码:

  1. Integer var1 = Integer.valueOf(1);
  2. int var2 = var1.intValue();
  3. Double var3 = Double.valueOf(1.0D);
  4. double var4 = var3.doubleValue();

可以发现, 整数的自动装箱实际上是调用了 Integer.valueOf() 方法, 拆箱实际上是调用了对象的 integer.intValue() 方法. 对于 Double, Character 等类型也是类似的.

总结:

  • 自动装箱, 调用包装类的 .valueOf()
  • 自动茶香, 调用包装类对象的 .xxValue()

那么, 回到第二个问题, 在包装类对象和基本数据类型进行 == 判断时:

  1. Integer n1 = 1;
  2. int n2 = 1;
  3. boolean bool1 = (n1 == n2);

通过反编译我们可以发现, 实际上是将包装类对象自动拆箱为基本数据类型来进行比较的:

  1. Integer var1 = Integer.valueOf(1);
  2. byte var2 = 1;
  3. boolean var3 = var1.intValue() == var2;

所以也就很好理解第二题返回 true.

Integer.valueOf()

我们接下来看 Integer 这个包装类的 valueOf() 方法:

  1. /**
  2. * Returns an {@code Integer} instance representing the specified
  3. * {@code int} value. If a new {@code Integer} instance is not
  4. * required, this method should generally be used in preference to
  5. * the constructor {@link #Integer(int)}, as this method is likely
  6. * to yield significantly better space and time performance by
  7. * caching frequently requested values.
  8. *
  9. * This method will always cache values in the range -128 to 127,
  10. * inclusive, and may cache other values outside of this range.
  11. *
  12. * @param i an {@code int} value.
  13. * @return an {@code Integer} instance representing {@code i}.
  14. * @since 1.5
  15. */
  16. public static Integer valueOf(int i) {
  17. if (i >= IntegerCache.low && i <= IntegerCache.high)
  18. return IntegerCache.cache[i + (-IntegerCache.low)];
  19. return new Integer(i);
  20. }

这个方法是根据 int i 的值来返回一个包装类实例.

有一点这里说的非常明白, “如果没有要求非要要一个新的包装类实例, 那么这个方法应该优先于构造器 new Integer() 使用, 因为这个方法缓存了经常使用的值, 所以很可能产生更好的时间和空间上的表现”, 这也是为什么在 JDK 9 之后就被注解为过期了的原因:

  1. /** @deprecated */
  2. @Deprecated(
  3. since = "9"
  4. )
  5. public Integer(int value) {
  6. this.value = value;
  7. }
  8. /** @deprecated */
  9. @Deprecated(
  10. since = "9"
  11. )

那么我们平常在使用时也应该尽量多用自动装箱或 valueOf() 这个方法.

具体缓存哪些数呢?

Integer 类内有一个嵌套类 IntegerCache, 顾名思义即整数缓存, 里面将 low ~ high 的所有整数都预先缓存在了一个名为 cacheInteger 数组内.

这里, 最小值 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 这两个仅仅相差一但匪夷所思的结果:

  1. Integer m = 127;
  2. Integer n = 127;
  3. System.out.println(m == n); // true
  4. Integer x = 128;
  5. Integer y = 128;
  6. System.out.println(x == y); // false