美文网首页
自动装箱/拆箱中的陷阱

自动装箱/拆箱中的陷阱

作者: shysheng | 来源:发表于2018-08-06 12:46 被阅读0次

    最近看到一个问题,很有意思,算是击中了自己的一个知识盲区,因此发出来跟大家分享一下。
    我们知道,在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   比较栈顶两引用型数值, 当结果不相等时跳转
    

    从这个简单的例子中,我们发现,==运算符比较的并不一定总是对象的引用,当遇到算数运算符时,经过装箱的对象将会被拆箱,这时实际比较的将是两者的数值大小。

    相关文章

      网友评论

          本文标题:自动装箱/拆箱中的陷阱

          本文链接:https://www.haomeiwen.com/subject/vnkwvftx.html