今天分享一道关于Integer的面试题。
1 面试题
面试题是这样的, 要手写打印结果:
public static void main(String[] args) {
Integer a = 1;
Integer b = 2;
Integer c = 3;
Integer d = 3;
Integer e = 128;
Integer f = 128;
Long g = 3L;
System.out.println(c == d);//true
System.out.println(e == f);//false
System.out.println(c.equals(d));//true
System.out.println(e.equals(f));//true
System.out.println(c == (a + b));//true
System.out.println(c.equals(a + b));//true
System.out.println(g == (a + b));//true
System.out.println(g.equals(a + b));//false
}
大家可以先自己手写一下结果,然后对比一下机器的运行结果,看看自己做对了几题。
我在深入研究这道题之前,以为很简单,对于这一块也是一知半解,深入研究之后才知道原来事情没那么简单。
2 装箱拆箱概念
在分析代码之前,我们需要先了解一些关于拆箱装箱的概念,如下一段代码:
public static void main(String[] args) {
Integer a = 1;//1 调用静态方法Integer.valueOf自动装箱
int b = a;//2调用对象方法Integer.intValue自动拆箱
}
javac后的class, 经过javap -c反编译后字节码如下:
public static void main(java.lang.String[]);
Code:
0: iconst_1
1: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
4: astore_1
5: aload_1
6: invokevirtual #3 // Method java/lang/Integer.intValue:()I
9: istore_2
10: return
我们看到在注释1处,调用了静态方法Integer.valueOf自动装箱:
Integer a = Integer.valufOf(1);
而在注释2处调用的是对象方法Integer.intValue自动拆箱:
int b = a.intValue();
自动装箱和拆箱其实就这么简单,但是加上== equals
以及运算符的话,使用起来就没那么简单了,下面开始分析面试题吧。
3 分析
我们拆分来看上面的面试题, 先看这么一段
public static void main(String[] args) {
Integer c = 3;
Integer d = 3;
Integer e = 128;
Integer f = 128;
System.out.println(c == d);//true
System.out.println(e == f);//false
System.out.println(c.equals(d));//true
System.out.println(e.equals(f));//true
}
我们知道==对于基本类型来讲是比较的值是否相等,对于引用数据来讲,比较的是地址值知否相等。equals就是比较的值是否相等。
那么上面的第一行和第二行的打印,可能有些人就有点蒙了。c == d 或者 e == f
都是对象自动装箱后比较的地址值,为什么一个是true, 一个是false呢?
想知道这个的根本原因就得看==两边的两个对象到底是什么,上面说了自动装箱调用的是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);
}
从源码中结合注释能看出来,调用valueOf方法的时候的逻辑是,如果i的值在[-128,127]区间内的话,是从IntegerCache的cache数组中取值,否则的话就是new Integer返回一个新的值。
我们看一下IntegerCache类,它是一个Integer的私有的静态内部类:
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) {
try {
int i = parseInt(integerCacheHighPropValue);
i = Math.max(i, 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;
cache = new Integer[(high - low) + 1];
int j = low;
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;
}
private IntegerCache() {}
}
从静态代码块中能够看出,IntegerCache类在加载的时候就缓存了从[-128,127]范围内的Integer对象到cache[]数组中,此数组一共有256个元素,
最小元素cache[0] = -128, 最大元素是cache[255] = 127;
分析到这里,上面的第一行和第二行的打印结果原因,自然就清晰了,因为3位于此cache数组缓存中,所以c和d是同一个对象,而e和f是128,超出了[-128,127]的取值范围,返回的是两个新new的对象,自然返回结果是false。
下面分析第三行第四行的打印结果, equals方法的源码如下:
public boolean equals(Object obj) {
if (obj instanceof Integer) {
return value == ((Integer)obj).intValue();
}
return false;
}
在使用equals比较的时候,首先判断比较的两个对象是否是同一种类型:
如果不是就直接返回false;如果是就拆箱比较它们的值是否相等。所以这里第三行和第四行因为类型一致,并且值的大小相等,所以结果都为true。
接着我们看面试题的第五第六行的分析:
public static void main(String[] args) {
Integer a = 1;
Integer b = 2;
Integer c = 3;
System.out.println(c == (a + b));//true
System.out.println(c.equals(a + b));//true
}
反编译后的字节码如下:
public static void main(java.lang.String[]);
Code:
0: iconst_1
1: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
4: astore_1
5: iconst_2
6: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
9: astore_2
10: iconst_3
11: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
14: astore_3
15: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
18: aload_3
19: invokevirtual #4 // Method java/lang/Integer.intValue:()I
22: aload_1
23: invokevirtual #4 // Method java/lang/Integer.intValue:()I
26: aload_2
27: invokevirtual #4 // Method java/lang/Integer.intValue:()I
30: iadd
31: if_icmpne 38
34: iconst_1
35: goto 39
38: iconst_0
39: invokevirtual #5 // Method java/io/PrintStream.println:(Z)V
42: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
45: aload_3
46: aload_1
47: invokevirtual #4 // Method java/lang/Integer.intValue:()I
50: aload_2
51: invokevirtual #4 // Method java/lang/Integer.intValue:()I
54: iadd
55: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
58: invokevirtual #6 // Method java/lang/Integer.equals:(Ljava/lang/Object;)Z
61: invokevirtual #5 // Method java/io/PrintStream.println:(Z)V
64: return
字节码中1 6 11行是对a b c 变量的装箱操作,我们这里不用关心。
对于c == (a + b)
打印结果: 在19 23 27行的时候,使用aload指令从局部变量表中取出上面调用astore存储在局部变量表中的元素放入操作数栈中,并且调用Integer.intValue()方法将其拆箱进行比较,那比较结果自然就和值是否相等有关了。所以第五行结果为true。
对于c.equals(a + b)
打印结果:从字节码的47 51行看出先是调用的Integer.intValue对a b进行拆箱后相加,然后调用Integer.valueOf进行装箱操作和c进行equals比较,上面分析过equals方法的源码,如果类型一致的话,比较的就是值是否相同,这里因为值相同,所以返回的就是true。
下面分析面试题的第七第八行:
public static void main(String[] args) {
Integer a = 1;
Integer b = 2;
Long g = 3L;
System.out.println(g == (a + b));//true
System.out.println(g.equals(a + b));//false
}
反编译后的字节码如下:
public static void main(java.lang.String[]);
Code:
0: iconst_1
1: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
4: astore_1
5: iconst_2
6: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
9: astore_2
10: ldc2_w #3 // long 3l
13: invokestatic #5 // Method java/lang/Long.valueOf:(J)Ljava/lang/Long;
16: astore_3
17: getstatic #6 // Field java/lang/System.out:Ljava/io/PrintStream;
20: aload_3
21: invokevirtual #7 // Method java/lang/Long.longValue:()J
24: aload_1
25: invokevirtual #8 // Method java/lang/Integer.intValue:()I
28: aload_2
29: invokevirtual #8 // Method java/lang/Integer.intValue:()I
32: iadd
33: i2l
34: lcmp
35: ifne 42
38: iconst_1
39: goto 43
42: iconst_0
43: invokevirtual #9 // Method java/io/PrintStream.println:(Z)V
46: getstatic #6 // Field java/lang/System.out:Ljava/io/PrintStream;
49: aload_3
50: aload_1
51: invokevirtual #8 // Method java/lang/Integer.intValue:()I
54: aload_2
55: invokevirtual #8 // Method java/lang/Integer.intValue:()I
58: iadd
59: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
62: invokevirtual #10 // Method java/lang/Long.equals:(Ljava/lang/Object;)Z
65: invokevirtual #9 // Method java/io/PrintStream.println:(Z)V
68: return
对于g == (a + b)
: 和上面的分析一样,21 25 29行都是拆箱操作,虽然g是Long类型,但是这里调用了longValue拆箱,最终比较的是值,所以结果为true。
对于g.equals(a + b)
:在51 55行对a b拆箱后相加,然后再装箱和Long.equals方法,Long.equals方法源码如下:
public boolean equals(Object obj) {
if (obj instanceof Long) {
return value == ((Long)obj).longValue();
}
return false;
}
从源码中可以看到和Long和Integer的equals方法一样,都是先比较对象类型是否一致,不一致就返回false, 一致的话就比较值,这里很明显类型不一致,因为3是Long类型,a+b装箱操作后的类型还是Integer类型,所以返回false。
最后将前面的面试题全部贴出来,附上分析:
public static void main(String[] args) {
Integer a = 1;
Integer b = 2;
Integer c = 3;
Integer d = 3;
Integer e = 128;
Integer f = 128;
Long g = 3L;
System.out.println(c == d);//true 3在[-128,127]区间 返回的是缓存,所以地址值一致,true
System.out.println(e == f);//false 128不在[-128,127]区间,返回的新new的Integer,所以false
System.out.println(c.equals(d));//true equals方法先看类型是否一致,然后比较的intValue,此处所以true
System.out.println(e.equals(f));//true equals方法在值相等的情况下,和值大小无关。先看类型,再比较值大小是否相等,此处为true
System.out.println(c == (a + b));//true ab拆箱相加,和拆箱后的c比较值,自然相等;就算大于127也是相等的,此处为true
System.out.println(c.equals(a + b));//true equals a b先拆箱,相加后装箱,先比较类型,再比较值,此处为true
System.out.println(g == (a + b));//true
System.out.println(g.equals(a + b));//false 因为这里g的类型是Long,和a+b装箱的类型Integer不一致,所以为false
}
4 总结
- 如果包装类型的==直接比较的话,比较的是地址值,比如Integer如果在[-128,127]之间的话,就是相等,否则就是new Integer,不相等
- 如果包装类型的equals直接比较的话,比较是否是同一种instance of类型,如果不是同一种类型的话直接返回false;然后intValue比较值是否相等。
- 如果包装类型和基本类型==比较的话,就拆箱比较(自行验证一下)
- 如果包装类型和基本类型equals的话,就装箱比较(自行验证一下)
5 扩展题
public static void main(String[] args) {
Long a = 1L;
Long b = 2L;
Long c = 3L;
Long d = 3L;
Long e = 128L;
Long f = 128L;
Double g = 3.0;
System.out.println(c == d);
System.out.println(e == f);
System.out.println(c.equals(d));
System.out.println(e.equals(f));
System.out.println(c == (a + b));
System.out.println(c.equals(a + b));
System.out.println(g == (a + b));
System.out.println(g.equals(a + b));
}
看完上面分析,相信你已经对装箱拆箱有了深入的理解,那么再做一道题练练手吧?
答案和上面面试题答案一致