重大线上事故!三元表达式引发的空指针问题( 二 )

  • If one of the second and third operands is of primitive type T, and the type of the other is the result of Applying boxing conversion (§5.1.7) to T, then the type of the conditional expression is T. 当第二,第三位操作数分别为基本类型和该基本类型对应的包装类型时,那么该表达式的结果的类型要求是基本类型
  • 为了满足以上规定,又避免程序员过度感知这个规则,所以在编译过程中编译器如果发现三目操作符的第二位和第三位操作数的类型分别是基本数据类型(如 boolean)以及该基本类型对应的包装类型(如 Boolean)时,并且需要返回表达式为包装类型,那么就需要对该包装类进行自动拆箱 。
    理解下这句话,JLS 的规范是如果第二和第三位操作数分别是基本类型和包装类型,那么要求返回值是基本类型 。那如果你自己写的代码返回值是包装类型,那么编译器为了满足 JLS 规范,其实是会自动做一个拆箱的
    简单总结:只要表达式 1 和表达式 2 的类型有一个是基本类型一个是包装类型,就会做触发类型对齐的拆箱操作 。
    下面再列举几个例子加深下理解:
    boolean flag = true;boolean simpleBoolean = false;Boolean objectBoolean = Boolean.FALSE;当第二位和第三位表达式都是包装类,表达式返回值也为包装类,编译器不需要做拆箱操作
    Boolean x1 = flag ? objectBoolean : objectBoolean;//反编译后代码(不需要做任何特殊操作)Boolean x1 = flag ? objectBoolean : objectBoolean;当第二位和第三位表达式都为基本类型时,表达式返回值也为基本类型,编译器不需要做拆箱操作
    boolean x2 = flag ? simpleBoolean : simpleBoolean;//反编译后代码(不需要做任何特殊操作)boolean x2 = flag ? simpleBoolean : simpleBoolean;当第二位和第三位表达式中一个为基本类型另一个为包装类型时,表达式返回值为基本类型,编译器需要做拆箱操作:
    boolean x3 = flag ? objectBoolean : simpleBoolean;//反编译后代码(需要对其中的包装类进行拆箱)boolean x3 = flag ? objectBoolean.booleanValue() : simpleBoolean;如果你清楚三目运算符的规则,那你就会正确地按照以上方式去定义 x1、x2 和 x3 的类型 。
    但是,并不是所有人都熟知这个规则,所以在实际应用中,还会出现以下几种定义方式:
    boolean x4 = flag ? objectBoolean : objectBoolean;// 反编译后代码(三元表达式的结果要求是包装类,而 x4 是基本类型,所以编译器需要做拆箱)boolean x4 = (flag ? objectBoolean : objectBoolean).booleanValue(); Boolean x5 = flag ? simpleBoolean : simpleBoolean;// 反编译后代码(三元表达式的结果要求是基本类型,而 x5 是包装类型,所以编译器需要做装箱)Boolean x5 = Boolean.valueOf(flag ? simpleBoolean : simpleBoolean); Boolean x6 = flag ? objectBoolean : simpleBoolean;// 反编译后代码(三元表达式的结果要求是基本类型,而 x5 是包装类型,所以编译器需要做装箱)Boolean x6 = Boolean.valueOf(flag ? objectBoolean.booleanValue() : simpleBoolean);所以,日常开发中就有可能出现以上 6 种情况 。在以上 6 种情况中,如果是涉及到自动拆箱的,一旦包装类的值为 null,即 null.booleanValue(),就必然会发生 NPE(装箱不会,因为装箱是 Boolean.valueOf(null),这并不会抛 NPE) 。
    小伙伴们可以把以上的 x3、x4 以及 x6 中的的包装类设置成 null,看看是不是会抛 NPE:
    boolean flag = true;boolean simpleBoolean = false;Boolean objectBoolean = Boolean.FALSE;// 将包装类设置为 nullBoolean nullBoolean = null;boolean x3 = flag ? nullBoolean : simpleBoolean;boolean x4 = flag ? nullBoolean : objectBoolean;Boolean x6 = flag ? nullBoolean : simpleBoolean;以上三种情况,都会在执行时发生 NPE:
    • 其中 x3 和 x6 是三目运算符运算过程中,根据 JLS 的规则确定类型的过程中要做自动拆箱而导致的 NPE 。由于使用了三目运算符,并且第二、第三位操作数分别是基本类型和对象 。就需要对对象进行拆箱操作,由于该对象为 null,所以在拆箱过程中调用 null.booleanValue() 的时候就报了 NPE 。
    • 而 x4 是因为三目运算符运算结束后根据规则他得到的是一个对象类型,但是在给变量赋值过程中进行自动拆箱所导致的 NPE 。




    推荐阅读