写在前面 之前经常看资料说不推荐滥用三元运算,刚好之前研究了字节码,那么我从字节码的角度看看三元运算为什么会有坑
正文 直接上示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 public class HelloWord { public static void main (String[] args) throws InterruptedException { Integer a = 1 ; Integer b = 2 ; Integer c = null ; Integer e = a; Boolean flag = false ; Integer result = (flag ? a * b : c); } }
运行后抛出NPE异常,那么为什么会出现NPE呢?我们直接用javap命令看看编译后的字节码,就可以看出原因来:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 public static void main (java.lang.String[]) throws java.lang.InterruptedException ; descriptor: ([Ljava/lang/String;)V flags: (0x0009 ) ACC_PUBLIC, ACC_STATIC Code: stack=2 , locals=7 , 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 : aconst_null 11 : astore_3 12 : aload_1 13 : astore 4 15 : iconst_0 16: invokestatic #3 // Method java/lang/Boolean.valueOf:(Z)Ljava/lang/Boolean; 19 : astore 5 21 : aload 5 23: invokevirtual #4 // Method java/lang/Boolean.booleanValue:()Z 26 : ifeq 41 29 : aload_1 30: invokevirtual #5 // Method java/lang/Integer.intValue:()I 33 : aload_2 34: invokevirtual #5 // Method java/lang/Integer.intValue:()I 37 : imul 38 : goto 45 41 : aload_3 42: invokevirtual #5 // Method java/lang/Integer.intValue:()I 45: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer; 48 : astore 6 50 : return
可以看到这里面会将Integer拆箱再装箱,所以才会报NPE,至于为什么会拆箱和装箱,是因为这里有a*b运算,那么整个三元运算符就会被编译器自动拆箱,如果把三元运算改成 Integer result = (flag ? a : c); 那么是不会出现NPE的,也就证明不会被拆箱
拓展 在《深入理解jvm虚拟机》书中也提到过自动拆箱和装箱,还留了一个题目让读者自己思考,那么我们也可以从字节码的角度和java源码角度直接看出答案而不需要运行程序
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 Integer a = 1 ; Integer b = 2 ; Integer c = 3 ; Integer d = 3 ; Integer e = 321 ; Integer f = 321 ; Long g = 3L ; System.out.println(c == d); System.out.println(e == 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));
字节码与答案(见注释)如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 public static void main (java.lang.String[]) throws java.lang.InterruptedException ; descriptor: ([Ljava/lang/String;)V flags: (0x0009 ) ACC_PUBLIC, ACC_STATIC Code: stack=5 , locals=8 , 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 : iconst_3 11: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer; 14 : astore_3 15 : iconst_3 16: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer; 19 : astore 4 21 : sipush 321 24: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer; 27 : astore 5 29 : sipush 321 32: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer; 35 : astore 6 37: ldc2_w #3 // long 3l 40: invokestatic #5 // Method java/lang/Long.valueOf:(J)Ljava/lang/Long; 43 : astore 7 45: getstatic #6 // Field java/lang/System.out:Ljava/io/PrintStream; 48 : aload_3 49 : aload 4 51 : if_acmpne 58 54 : iconst_1 55 : goto 59 58 : iconst_0 59: invokevirtual #7 // Method java/io/PrintStream.println:(Z)V 62: getstatic #6 // Field java/lang/System.out:Ljava/io/PrintStream; 65 : aload 5 67 : aload 6 69 : if_acmpne 76 72 : iconst_1 73 : goto 77 76 : iconst_0 77: invokevirtual #7 // Method java/io/PrintStream.println:(Z)V 80: getstatic #6 // Field java/lang/System.out:Ljava/io/PrintStream; 83 : aload_3 84: invokevirtual #8 // Method java/lang/Integer.intValue:()I 87 : aload_1 88: invokevirtual #8 // Method java/lang/Integer.intValue:()I 91 : aload_2 92: invokevirtual #8 // Method java/lang/Integer.intValue:()I 95 : iadd 96 : if_icmpne 103 99 : iconst_1 100 : goto 104 103 : iconst_0 104: invokevirtual #7 // Method java/io/PrintStream.println:(Z)V 107: getstatic #6 // Field java/lang/System.out:Ljava/io/PrintStream; 110 : aload_3 111 : aload_1 112: invokevirtual #8 // Method java/lang/Integer.intValue:()I 115 : aload_2 116: invokevirtual #8 // Method java/lang/Integer.intValue:()I 119 : iadd 120: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer; 123: invokevirtual #9 // Method java/lang/Integer.equals:(Ljava/lang/Object;)Z 126: invokevirtual #7 // Method java/io/PrintStream.println:(Z)V 129: getstatic #6 // Field java/lang/System.out:Ljava/io/PrintStream; 132 : aload 7 134: invokevirtual #10 // Method java/lang/Long.longValue:()J 137 : aload_1 138: invokevirtual #8 // Method java/lang/Integer.intValue:()I 141 : aload_2 142: invokevirtual #8 // Method java/lang/Integer.intValue:()I 145 : iadd 146 : i2l 147 : lcmp 148 : ifne 155 151 : iconst_1 152 : goto 156 155 : iconst_0 156: invokevirtual #7 // Method java/io/PrintStream.println:(Z)V 159: getstatic #6 // Field java/lang/System.out:Ljava/io/PrintStream; 162 : aload 7 164 : aload_1 165: invokevirtual #8 // Method java/lang/Integer.intValue:()I 168 : aload_2 169: invokevirtual #8 // Method java/lang/Integer.intValue:()I 172 : iadd 173: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer; 176: invokevirtual #11 // Method java/lang/Long.equals:(Ljava/lang/Object;)Z 179: invokevirtual #7 // Method java/io/PrintStream.println:(Z)V 182 : return
所以在日常工作中遇到包装类型的是需要一再仔细和认真,警惕字段拆装箱的陷阱。