从阿里规约谈起 - 包装类的值比较注意事项

前言

intlong 等基本数据类型值比较可以直接使用 == 比较,其包装类型:IntegerLong 如果直接使用 == 比较大小我们会发现一个神奇的现象。当然,阿里规约也对此做出强制规定:

【强制】 所有的相同类型的包装类对象之间值的比较,全部使用 equals 方法比较。

现象

 1Integer integer1 = 3;
 2Integer integer2 = 3;
 3if (integer1 == integer2)
 4    System.out.println("integer1 == integer2");
 5else
 6    System.out.println("integer1 != integer2");
 7
 8Integer integer3 = 300;
 9Integer integer4 = 300;
10if (integer3 == integer4)
11    System.out.println("integer3 == integer4");
12else
13    System.out.println("integer3 != integer4");
14
15Integer integer5 = new Integer(3);
16Integer integer6 = new Integer(3);
17if (integer5 == integer6)
18    System.out.println("integer5 == integer6");
19else
20    System.out.println("integer5 != integer6");
21
22// 输出结果:
23// integer1 == integer2
24// integer3 != integer4
25// integer5 != integer6

这是一个很神奇的结果,特别是对于第一二组对照,几乎相同的代码,只是值的变化却有不同的结果。

原因

对于第一二组的写法,编译器会自动装箱,即调用 valueOf 方法。理所应当我们要看看 valueOf 代码有什么秘密。

 1public 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}
 6
 7private static class IntegerCache {
 8    static final int low = -128;
 9    static final int high;
10    static final Integer cache[];
11
12    static {
13        int h = 127;
14        String integerCacheHighPropValue =
15            sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
16        if (integerCacheHighPropValue != null) {
17            try {
18                int i = parseInt(integerCacheHighPropValue);
19                i = Math.max(i, 127);
20                // Maximum array size is Integer.MAX_VALUE
21                h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
22            } catch( NumberFormatException nfe) {
23                // If the property cannot be parsed into an int, ignore it.
24            }
25        }
26        high = h;
27
28        cache = new Integer[(high - low) + 1];
29        int j = low;
30        for(int k = 0; k < cache.length; k++)
31            cache[k] = new Integer(j++);
32        assert IntegerCache.high >= 127;
33    }
34
35    private IntegerCache() {}
36}

看到这就能明白为什么第一二对照组的结果差异了:当在 [-128,127] 之间赋值时,会被缓存到 IntegerCache 中,所以两个对象是同一个对象,而在此之外的值则是新生成一个对象,通过 == 判断自然为 false

结论

实际通过进一步阅读代码可以得知,所有整数类型的包装类都有类似的缓存机制:

  • Byte - ByteCache
  • Short - ShortCache
  • Long - LongCache
  • Character - CharacterCache

为了避免这种诡异情况发生,我们遵循对象的比较方式,对于包装类的值比较用 equals 方法即可。我们可以通过 equals 源码来确认一下结论的正确性:

 1public boolean equals(Object obj) {
 2    if (obj instanceof Integer) {
 3        return value == ((Integer)obj).intValue();
 4    }
 5    return false;
 6}
 7
 8public int intValue() {
 9    return value;
10}

可见 equals 是通过调用 intValue 拆箱得到基本数据类型然后比较的,可以保证值比较的正确性。

当然除了阿里规约推荐的 equals 以外,也可以通过包装类型的比较大小方法 compareTo 去比较:

1public int compareTo(Integer anotherInteger) {
2    return compare(this.value, anotherInteger.value);
3}
4
5public static int compare(int x, int y) {
6    return (x < y) ? -1 : ((x == y) ? 0 : 1);
7}

compareTo 直接取值进行两者比较,根据大小分别返回 -1、0 和 1。