当熟悉的三元表达式遇到装/拆箱发生了未预料到的NPE。
以下代码都是在1.8.0_301版本下运行
举个🌰
package net.gaox;
public class TernaryOperation4NPE {
public static void main(String[] args) {
Integer a = 10;
Integer b = 20;
Integer c = null;
// 这里会抛出NPE
Integer d = false ? a * b : c;
System.out.println("拦精灵");
}
}
猜测由于a*b,Integer都进行了拆箱,c因为是null,进行拆箱时就抛出了NPE,为了验证,我们使用javap -c
命令看下简单的字节码信息
javap -c TernaryOperation4NPE ──(Sat,Aug21)─┘
Compiled from "TernaryOperation4NPE.java"
public class TernaryOperation4NPE {
public TernaryOperation4NPE();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: bipush 10
2: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
5: astore_1
6: bipush 20
8: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
11: astore_2
12: aconst_null
13: astore_3
14: aload_3
15: invokevirtual #3 // Method java/lang/Integer.intValue:()I
18: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
21: astore 4
23: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
26: ldc #5 // String 拦精灵
28: invokevirtual #6 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
31: return
}
如图,第11—18行即c的操作过程,可以看出第15行Integer.intValue
获取c的Integer值就是抛出NPE
的罪魁祸首。
但是有个疑问,很明显可以看出,经过javac
的优化,a、b根本不会执行,a、b也都没有拆箱的操作,为什么要对c进行拆箱呢,直接将null赋值给d不是更简捷,仅仅因为代码中使用了a、b进行乘法运算就导致了c的拆箱吗?
真的是由于自动拆箱的过度优化吗
我们来做个验证,把抛出异常的代码修改为Integer d = false ? null : c;
,此时代码正常可以运行。再来看下字节码信息:
6: bipush 20
8: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
11: astore_2
12: aconst_null
13: astore_3
14: aload_3
15: astore 4
17: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
20: ldc #4 // String 拦精灵
22: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
25: return
可以看到确实没有对c的Integer.intValue
方法的调用,之前对c的拆箱确实是由于ab运算导致的。至此,我们可以得出一个猜想,*在三元表达式中,如果一个结果执行了数学运算,即使表达式的判断条件短路了此运算,另外一个结果也会由于拆箱而可能导致NPE的发生。
第一个返回值是基本类型呢
我们再来做个验证,把抛出异常的代码修改为Integer d = false ? 30 : c;
,此时代码却又不能运行了。再来看下字节码信息:
8: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
11: astore_2
12: aconst_null
13: astore_3
14: aload_3
15: invokevirtual #3 // Method java/lang/Integer.intValue:()I
18: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
21: astore 4
23: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
26: ldc #5 // String 拦精灵
28: invokevirtual #6 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
31: return
可以看出依然是第15行Integer.intValue
获取c的Integer值,造成了NPE。
代码补充
第二处代码
package net.gaox;
public class TernaryOperation4NPE {
public static void main(String[] args) {
Integer a = 10;
Integer b = 20;
Integer c = null;
Integer d = false ? null : c;
System.out.println("拦精灵");
}
}
第三处代码
package net.gaox;
public class TernaryOperation4NPE {
public static void main(String[] args) {
Integer a = 10;
Integer b = 20;
Integer c = null;
// 这里会抛出NPE
Integer d = false ? 30 : c;
System.out.println("拦精灵");
}
}