自动装箱/拆箱中的陷阱
2018-08-06 本文已影响0人
shysheng
最近看到一个问题,很有意思,算是击中了自己的一个知识盲区,因此发出来跟大家分享一下。
我们知道,在java中,== 运算符比较的是两个对象的引用,同时对一些经常需要用打的小对象(-128~127),在内存中是复用的,因此在下面这段代码中,将分别输出true和false。
public static void main(String[] args) {
Integer a = 1;
Integer b = 1;
Integer c = 1000;
Integer d = 1000;
System.out.println(a == b); // true
System.out.println(c == d); // false
}
基于这个认识,当我第一次看到下面这份代码时,我本能地认为将输出false和true,然而实际运行结果却让我大跌眼镜,第一个表达式的结果竟然也是true!!!难道说一个Long型常量3和一个Integer型常量3指向的是同一个引用?
public class BoxTest {
public static void main(String[] args) {
Integer a = 1;
Integer b = 2;
Long c = 3L;
Long d = 3L;
System.out.println(c == a + b); // true
System.out.println(c == d); // true
}
}
为了一探究竟,我决定先反编译下对应的class文件,结果如下。可以看到,表达式c == a + b经过编译后,Long型常量和Integer型常量都被自动拆箱了,这时==运算符比较的其实是两者的数值大小。这跟自己之前了解到的似乎不太一样,于是决定继续看看对应的字节码作进一步的验证。
public class BoxTest {
public BoxTest() {
}
public static void main(String[] args) {
Integer a = Integer.valueOf(1);
Integer b = Integer.valueOf(2);
Long c = Long.valueOf(3L);
Long d = Long.valueOf(3L);
System.out.println(c.longValue() == (long)(a.intValue() + b.intValue()));
System.out.println(c == d);
}
}
运用javap工具,得到对应的字节码如下。不难看出:
1.0~37字节是将4个常量做装箱操作并赋值给a/b/c/d;
2.40~41字节是将栈顶的a/b相加并强转为long型;
3.最关键的两个比较操作是42字节处的指令lcmp和60字节处的指令if_acmpne
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=5, locals=5, args_size=1
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: ldc2_w #3 // long 3l
20: invokestatic #5 // Method java/lang/Long.valueOf:(J)Ljava/lang/Long;
23: astore 4
25: getstatic #6 // Field java/lang/System.out:Ljava/io/PrintStream;
28: aload_3
29: invokevirtual #7 // Method java/lang/Long.longValue:()J
32: aload_1
33: invokevirtual #8 // Method java/lang/Integer.intValue:()I
36: aload_2
37: invokevirtual #8 // Method java/lang/Integer.intValue:()I
40: iadd
41: i2l
42: lcmp
43: ifne 50
46: iconst_1
47: goto 51
50: iconst_0
51: invokevirtual #9 // Method java/io/PrintStream.println:(Z)V
54: getstatic #6 // Field java/lang/System.out:Ljava/io/PrintStream;
57: aload_3
58: aload 4
60: if_acmpne 67
63: iconst_1
64: goto 68
67: iconst_0
68: invokevirtual #9 // Method java/io/PrintStream.println:(Z)V
71: return
lcmp和if_acmpne这两个操作指令的含义如下,跟我们猜想的一致。
lcmp 比较栈顶两long型数值大小, 并将结果(1, 0或-1)压入栈顶
if_acmpne 比较栈顶两引用型数值, 当结果不相等时跳转
从这个简单的例子中,我们发现,==运算符比较的并不一定总是对象的引用,当遇到算数运算符时,经过装箱的对象将会被拆箱,这时实际比较的将是两者的数值大小。