当熟悉的三元表达式遇到装/拆箱发生了未预料到的NPE。

以下代码都是在1.8.0_301版本下运行

举个🌰

  1. package net.gaox;
  2. public class TernaryOperation4NPE {
  3. public static void main(String[] args) {
  4. Integer a = 10;
  5. Integer b = 20;
  6. Integer c = null;
  7. // 这里会抛出NPE
  8. Integer d = false ? a * b : c;
  9. System.out.println("拦精灵");
  10. }
  11. }

猜测由于a*b,Integer都进行了拆箱,c因为是null,进行拆箱时就抛出了NPE,为了验证,我们使用javap -c命令看下简单的字节码信息

  1. javap -c TernaryOperation4NPE ──(Sat,Aug21)─┘
  2. Compiled from "TernaryOperation4NPE.java"
  3. public class TernaryOperation4NPE {
  4. public TernaryOperation4NPE();
  5. Code:
  6. 0: aload_0
  7. 1: invokespecial #1 // Method java/lang/Object."<init>":()V
  8. 4: return
  9. public static void main(java.lang.String[]);
  10. Code:
  11. 0: bipush 10
  12. 2: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
  13. 5: astore_1
  14. 6: bipush 20
  15. 8: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
  16. 11: astore_2
  17. 12: aconst_null
  18. 13: astore_3
  19. 14: aload_3
  20. 15: invokevirtual #3 // Method java/lang/Integer.intValue:()I
  21. 18: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
  22. 21: astore 4
  23. 23: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
  24. 26: ldc #5 // String 拦精灵
  25. 28: invokevirtual #6 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
  26. 31: return
  27. }

如图,第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;,此时代码正常可以运行。再来看下字节码信息:

  1. 6: bipush 20
  2. 8: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
  3. 11: astore_2
  4. 12: aconst_null
  5. 13: astore_3
  6. 14: aload_3
  7. 15: astore 4
  8. 17: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
  9. 20: ldc #4 // String 拦精灵
  10. 22: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
  11. 25: return

可以看到确实没有对c的Integer.intValue方法的调用,之前对c的拆箱确实是由于ab运算导致的。至此,我们可以得出一个猜想,*在三元表达式中,如果一个结果执行了数学运算,即使表达式的判断条件短路了此运算,另外一个结果也会由于拆箱而可能导致NPE的发生

第一个返回值是基本类型呢

我们再来做个验证,把抛出异常的代码修改为Integer d = false ? 30 : c;,此时代码却又不能运行了。再来看下字节码信息:

  1. 8: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
  2. 11: astore_2
  3. 12: aconst_null
  4. 13: astore_3
  5. 14: aload_3
  6. 15: invokevirtual #3 // Method java/lang/Integer.intValue:()I
  7. 18: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
  8. 21: astore 4
  9. 23: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
  10. 26: ldc #5 // String 拦精灵
  11. 28: invokevirtual #6 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
  12. 31: return

可以看出依然是第15行Integer.intValue获取c的Integer值,造成了NPE。

代码补充

第二处代码

  1. package net.gaox;
  2. public class TernaryOperation4NPE {
  3. public static void main(String[] args) {
  4. Integer a = 10;
  5. Integer b = 20;
  6. Integer c = null;
  7. Integer d = false ? null : c;
  8. System.out.println("拦精灵");
  9. }
  10. }

第三处代码

  1. package net.gaox;
  2. public class TernaryOperation4NPE {
  3. public static void main(String[] args) {
  4. Integer a = 10;
  5. Integer b = 20;
  6. Integer c = null;
  7. // 这里会抛出NPE
  8. Integer d = false ? 30 : c;
  9. System.out.println("拦精灵");
  10. }
  11. }