前言

上篇文章讲的是基本数据类型,本章其实是从上一篇文章中抽出来的,上文篇幅过大,所以就把这部分内容单独抽出来了。

本文主要分析 Integer 和 int 之间的比较问题,接下来慢慢展开。

版本约定

我们先来看一段代码,猜猜它的执行结果是什么样子?

  1. public static void main(String[] args) {
  2. Integer a = new Integer(123);
  3. Integer b = new Integer(123);
  4. System.out.println("a == b: " + (a == b));
  5. Integer c = Integer.valueOf(123);
  6. Integer d = Integer.valueOf(123);
  7. System.out.println("c == d: " + (c == d));
  8. Integer e = Integer.valueOf(128);
  9. Integer f = Integer.valueOf(128);
  10. System.out.println("e == f: " + (e == f));
  11. }

运行程序,输出:

  1. a == b: false
  2. c == d: true
  3. e == f: false

这里的执行结果和我们想象的结果有出入,有两点疑问:

  1. 为什么 new Integer(123) 和 Integer.valueOf(123) 的执行结果不一样?
  2. 为什么 Integer.valueOf(123) 和 Integer.valueOf(128) 的执行结果不一样?

带着这两个问题,我们来看看 Integer 类的源码,先看下 valueOf() 方法的源码。

  1. public static Integer valueOf(int i) {
  2. if (i >= IntegerCache.low && i <= IntegerCache.high)
  3. return IntegerCache.cache[i + (-IntegerCache.low)];
  4. return new Integer(i);
  5. }

IntegerCache 是什么?看看它的源码内容。

  1. /**
  2. * Cache to support the object identity semantics of autoboxing for values b
  3. * -128 and 127 (inclusive) as required by JLS.
  4. *
  5. * The cache is initialized on first usage. The size of the cache
  6. * may be controlled by the {@code -XX:AutoBoxCacheMax=<size>} option.
  7. * During VM initialization, java.lang.Integer.IntegerCache.high property
  8. * may be set and saved in the private system properties in the
  9. * sun.misc.VM class.
  10. */
  11. private static class IntegerCache {
  12. static final int low = -128;
  13. static final int high;
  14. static final Integer cache[];
  15. static {
  16. // high value may be configured by property
  17. int h = 127;
  18. String integerCacheHighPropValue =
  19. sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.hig
  20. if (integerCacheHighPropValue != null) {
  21. try {
  22. int i = parseInt(integerCacheHighPropValue);
  23. i = Math.max(i, 127);
  24. // Maximum array size is Integer.MAX_VALUE
  25. h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
  26. } catch( NumberFormatException nfe) {
  27. // If the property cannot be parsed into an int, ignore it.
  28. }
  29. }
  30. high = h;
  31. cache = new Integer[(high - low) + 1];
  32. int j = low;
  33. for(int k = 0; k < cache.length; k++)
  34. cache[k] = new Integer(j++);
  35. // range [-128, 127] must be interned (JLS7 5.1.7)
  36. assert IntegerCache.high >= 127;
  37. }
  38. private IntegerCache() {}
  39. }

根据注释描述,我们知道 IntegerCache 是 Integer 的一个内部缓存池,默认缓存 -128 到 127 的整数,另外描述中还讲到自动装箱的概念,下面会提到。

我们再看 valueOf() 方法就知道了,它的实现比较简单,就是先判断值是否在缓存池中,如果在的话就直接返回缓存池的内容。

这样,上面的两个问题,你应该就知道原因了吧。

接下来,我们来看上面提到的自动封装的概念,下面的代码,大家一定不会陌生。

  1. public static void main(String[] args) {
  2. Integer a = 1;
  3. System.out.println(a);
  4. }

我们经常这样初始化 Integer 类型,编译器在把 int 类型的数据封装成 Integer 类型的操作就叫做自动装箱。

编译器会在自动装箱过程中会调用 valueOf() 方法,因此多个 Integer 实例使用自动装箱来创建并且值相同(范围:-128 ~ 127),那么就会引用相同的对象。

测试代码:

  1. public static void main(String[] args) {
  2. Integer a = 123;
  3. Integer b = 123;
  4. System.out.println("a == b: " + (a == b));
  5. }

运行程序,输出:

  1. a == b: true

其他基本类型对应的引用类型是否也存在缓存池呢?它的缓存范围是多少呢?总结如下:

  • boolean values true and false
  • all byte values
  • short values between -128 and 127
  • int values between -128 and 127
  • char in the range \u0000 to \u007F

    Integer 和 int 的比较

结合上面的源码分析,我们可以得出如下结论:

  1. 当 int 类型和它的引用类型 Integer 比较的时候,Integer 会自动拆箱,转换为 int 类型进行比较,所以可以直接使用 **==** 进行比较是否相等。

测试代码:

  1. public static void main(String[] args) {
  2. int a = 200;
  3. Integer b = 200;
  4. Integer c = new Integer(200);
  5. System.out.println("a == b: " + (a == b));
  6. System.out.println("a == c: " + (a == c));
  7. }

运行程序,输出:

  1. a == b: true
  2. a == c: true
  1. 当是两个引用类型 Integer 比较的时候,使用 equals 方法进行比较,否则容易出现意想不到的结果。

测试代码:

  1. public static void main(String[] args) {
  2. Integer a = 127;
  3. Integer b = 127;
  4. System.out.println("a == b : " + (a == b));
  5. System.out.println("a.equals(b): " + a.equals(b));
  6. Integer c = 128;
  7. Integer d = 128;
  8. System.out.println("c == d: " + (c == d));
  9. System.out.println("c.equals(d): " + c.equals(d));
  10. Integer e = 127;
  11. Integer f = new Integer(127);
  12. System.out.println("e == f: " + (e == f));
  13. System.out.println("e.equals(f): " + e.equals(f));
  14. }

运行程序,输出:

  1. a == b : true
  2. a.equals(b): true
  3. c == d: false
  4. c.equals(d): true
  5. e == f: false
  6. e.equals(f): true

总结

这种看似很小的问题也不能忽视,代码执行正确与否,很多时候在于细节的处理。

上面有些内容是结论性的,没有太过讲细,有些东西我也不太清楚,比如为什么当 int 类型和它的引用类型 Integer 比较的时候,Integer 会自动拆箱?这个应该和编译后的字节码有关了,需要分析编译后的 class 文件。

作者:殷建卫 链接:https://www.yuque.com/yinjianwei/vyrvkf/vxobtk 来源:殷建卫 - 架构笔记 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。