面试中经常被问的一个问题就是Integer类型的对象的比较问题,今天我们就来一探究竟
有如下代码:问输出是什么?
public static void main(String[] args) {
Integer a = 128;
Integer b = 128;
Integer c = 1;
Integer d = 1;
System.out.println(a==b);
System.out.println(a.equals(b));
System.out.println(c==d);
System.out.println(c.equals(d))
}
//输出:
false
true
true
true
1、equals()
首先我们看比较容易理解的a.equals(b)
和c.equals(d)
,我们看下源码,主要涉及代码如下:
private final int value;
public boolean equals(Object obj) {
if (obj instanceof Integer) {
return value == ((Integer)obj).intValue();
}
return false;
}
public int intValue() {
return value;
}
可以看到Integer中重写了equals
方法,其中就是对于int类型值的比较,所以自然返回true
System.out.println(a.equals(b));//等价于128==128
System.out.println(c.equals(d));//等价于1==1
2、==
下面的==
比较就比较麻烦了,其实也不麻烦,只是涉及到自动拆箱和Integer对象缓存,
我们先来看一下字节码:javap -v 反编译一下,找到main
方法对用的字节码如下:
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=3, locals=5, args_size=1
0: sipush 128
3: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
6: astore_1
7: sipush 128
10: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
13: astore_2
14: iconst_1
15: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
18: astore_3
19: iconst_1
20: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
23: astore 4
25: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
28: aload_1
29: aload_2
30: if_acmpne 37
33: iconst_1
34: goto 38
37: iconst_0
38: invokevirtual #4 // Method java/io/PrintStream.println:(Z)V
41: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
44: aload_1
45: aload_2
46: invokevirtual #5 // Method java/lang/Integer.equals:(Ljava/lang/Object;)Z
49: invokevirtual #4 // Method java/io/PrintStream.println:(Z)V
52: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
55: aload_3
56: aload 4
58: if_acmpne 65
61: iconst_1
62: goto 66
65: iconst_0
66: invokevirtual #4 // Method java/io/PrintStream.println:(Z)V
69: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
72: aload_3
73: aload 4
75: invokevirtual #5 // Method java/lang/Integer.equals:(Ljava/lang/Object;)Z
78: invokevirtual #4 // Method java/io/PrintStream.println:(Z)V
81: return
通过字节码可以看到,从0-23是在初始化了4个变量,同时存储之前自动调用了java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
进行装箱,25-38对应代码System.out.println(a==b);
25: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
28: aload_1 //加载128
29: aload_2 //加载128
30: if_acmpne 37 //比较两个Integer引用是否是!=,成立执行33行,将1写入操作数栈,不成立跳转到37,将0写入操作数栈
33: iconst_1
34: goto 38
37: iconst_0
38: invokevirtual #4 // Method java/io/PrintStream.println:(Z)V//打印30行执行后的结果0或1对应false或true
问题就出现这里,通过输出的结果我们可以看到a和b引用的是不同的Integer,这个可以理解,因为我们就是定义了两个Integer;但是c和d引用的却是同一个Integer,那为什么会这样呢?
public static void main(String[] args) {
Integer a = 128;
Integer b = 128;
Integer c = 1;
Integer d = 1;
System.out.println(a==b);
System.out.println(a.equals(b));
System.out.println(c==d);
System.out.println(c.equals(d))
}
//输出:
false
true
true
true
玄机应该是在创建对象的时候,编译器悄悄的干了点什么,干了什么呢?其实就是自动装箱,我们找到自动装箱执行的方法Integer.valueOf
的源代码如下:
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
可以看到,当i
在[IntegerCache.low, IntegerCache.high]
中时,没有new新的Integer对象,所以我们在找到IntegerCache
一探究竟,源代码如下:
/**
* 缓存以支持自动装箱的对象标识语义
* JLS 要求的 -128 和 127(含)。
*
* 缓存在第一次使用时初始化。 缓存大小
* 可以由 {@code -XX:AutoBoxCacheMax=<size>} 选项控制。
* 在 VM 初始化期间,java.lang.Integer.IntegerCache.high 属性
* 可以在私有系统属性中设置和保存
* sun.misc.VM 类。
*/
private static class IntegerCache {
static final int low = -128;//缓存的最小值
static final int high;
static final Integer cache[];
static {
// high value may be configured by property
int h = 127;
String integerCacheHighPropValue =
sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");//可以自己配置缓存的最大值
if (integerCacheHighPropValue != null) {//如果自己配置了就用自己配置的,同时下面代码保证了,自己配置的值要大于127才生效
try {
int i = parseInt(integerCacheHighPropValue);
i = Math.max(i, 127);//大于127才生效
// Maximum array size is Integer.MAX_VALUE
h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
} catch( NumberFormatException nfe) {
// If the property cannot be parsed into an int, ignore it.
}
}
high = h;//没有自己配置就用默认128
//创建缓存数组
cache = new Integer[(high - low) + 1];
int j = low;
//缓存数组中全部填充值Integer对象
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);
// range [-128, 127] must be interned (JLS7 5.1.7)
assert IntegerCache.high >= 127;//最大值必须大于127,否则报错。防止自己配置错误
}
private IntegerCache() {}
}
代码中都加了注解,应该很好理解。
看到这里应该就很明白了:得出结论
(1)System.out.println(a==b);
中,因为128>127所以,在自动装箱执行valueOf
方法时时并没有进入if语句,而是创建的新的Integer对象,所以显示a、b是两个对象的引用,所以输出false。
(2)同理 System.out.println(c==d);
中,1<127,所以进入if语句块,c、d对象都是IntegerCache.cache[1]
引用的对象,所以自然是相等的!输出true。
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
public static void main(String[] args) {
Integer a = 128;
Integer b = 128;
Integer c = 1;
Integer d = 1;
System.out.println(a==b);
System.out.println(a.equals(b));
System.out.println(c==d);
System.out.println(c.equals(d))
}
//输出:
false
true
true
true
到了这里其实还没有完:如下代码会输出什么呢?
public static void main(String[] args) {
Integer e = new Integer(128);
Integer f = new Integer(128);
Integer g = new Integer(1);
Integer h = new Integer(1);
System.out.println(e==f);
System.out.println(e.equals(f));
System.out.println(g==h);
System.out.println(g.equals(h));
}
使用new创建对象是不会触发自动装箱操作的,所以预测会打印false、true、false、true,执行结果如下,和预料的一样。
false
true
false
true
到这里所有关于Integer对象的比较问题就都说清楚了,但是还会有a与e比较、c与g比较的问题,这些衍生问题就很容易了。
最后再总结一下:==比较的地址,equals方法得看代码怎么写(一般是值的比较)
that is all!!!