当熟悉的三元表达式遇到装/拆箱发生了未预料到的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;// 这里会抛出NPEInteger 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_01: invokespecial #1 // Method java/lang/Object."<init>":()V4: returnpublic static void main(java.lang.String[]);Code:0: bipush 102: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;5: astore_16: bipush 208: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;11: astore_212: aconst_null13: astore_314: aload_315: invokevirtual #3 // Method java/lang/Integer.intValue:()I18: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;21: astore 423: 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;)V31: 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 208: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;11: astore_212: aconst_null13: astore_314: aload_315: astore 417: 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;)V25: 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_212: aconst_null13: astore_314: aload_315: invokevirtual #3 // Method java/lang/Integer.intValue:()I18: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;21: astore 423: 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;)V31: 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;// 这里会抛出NPEInteger d = false ? 30 : c;System.out.println("拦精灵");}}
