138

为什么这会抛出NullPointerException

public static void main(String[] args) throws Exception {
    Boolean b = true ? returnsNull() : false; // NPE on this line.
    System.out.println(b);
}

public static Boolean returnsNull() {
    return null;
}

虽然这不是

public static void main(String[] args) throws Exception {
    Boolean b = true ? null : false;
    System.out.println(b); // null
}

?

解决方案是通过替换为避免被拆箱的方式 - 这falseBoolean.FALSE不可能nullboolean。但这不是问题。问题是为什么?JLS 中是否有任何参考资料证实了这种行为,尤其是第二种情况?

4

4 回答 4

95

The difference is that the explicit type of the returnsNull() method affects the static typing of the expressions at compile time:

E1: `true ? returnsNull() : false` - boolean (auto-unboxing 2nd operand to boolean)

E2: `true ? null : false` - Boolean (autoboxing of 3rd operand to Boolean)

See Java Language Specification, section 15.25 Conditional Operator ? :

  • For E1, the types of the 2nd and 3rd operands are Boolean and boolean respectively, so this clause applies:

    If one of the second and third operands is of type boolean and the type of the other is of type Boolean, then the type of the conditional expression is boolean.

    Since the type of the expression is boolean, the 2nd operand must be coerced to boolean. The compiler inserts auto-unboxing code to the 2nd operand (return value of returnsNull()) to make it type boolean. This of course causes the NPE from the null returned at run-time.

  • For E2, types of the 2nd and 3rd operands are <special null type> (not Boolean as in E1!) and boolean respectively, so no specific typing clause applies (go read 'em!), so the final "otherwise" clause applies:

    Otherwise, the second and third operands are of types S1 and S2 respectively. Let T1 be the type that results from applying boxing conversion to S1, and let T2 be the type that results from applying boxing conversion to S2. The type of the conditional expression is the result of applying capture conversion (§5.1.10) to lub(T1, T2) (§15.12.2.7).

    • S1 == <special null type> (see §4.1)
    • S2 == boolean
    • T1 == box(S1) == <special null type> (see last item in list of boxing conversions in §5.1.7)
    • T2 == box(S2) == `Boolean
    • lub(T1, T2) == Boolean

    So the type of the conditional expression is Boolean and the 3rd operand must be coerced to Boolean. The compiler inserts auto-boxing code for the 3rd operand (false). The 2nd operand doesn't need the auto-unboxing as in E1, so no auto-unboxing NPE when null is returned.


This question needs a similar type analysis:

Java conditional operator ?: result type

于 2010-10-07T14:00:20.700 回答
28

该行:

    Boolean b = true ? returnsNull() : false;

在内部转换为:

    Boolean b = true ? returnsNull().booleanValue() : false; 

执行拆箱;因此:null.booleanValue()将产生一个 NPE

这是使用自动装箱时的主要缺陷之一。这种行为确实记录在5.1.8 JLS

编辑:我相信拆箱是由于第三个运算符是布尔类型的,比如(添加了隐式转换):

   Boolean b = (Boolean) true ? true : false; 
于 2010-10-07T13:38:57.123 回答
17

来自Java 语言规范,第 15.25 节

  • 如果第二个和第三个操作数之一是布尔类型,另一个是布尔类型,则条件表达式的类型是布尔类型。

因此,第一个示例尝试调用Boolean.booleanValue()以便按照第一条规则Boolean进行转换。boolean

在第二种情况下,第一个操作数是 null 类型,当第二个不是引用类型时,应用自动装箱转换:

  • 否则,第二个和第三个操作数分别是 S1 和 S2 类型。令 T1 为对 S1 应用装箱转换产生的类型,令 T2 为对 S2 应用装箱转换产生的类型。条件表达式的类型是将捕获转换 (§5.1.10) 应用于 lub(T1, T2) (§15.12.2.7) 的结果。
于 2010-10-07T13:41:27.430 回答
0

我们可以从字节码中看出这个问题。在 main 字节码的第 3 行3: invokevirtual #3 // Method java/lang/Boolean.booleanValue:()Z,值 null 的装箱布尔值,invokevirtual方法java.lang.Boolean.booleanValue,它当然会抛出 NPE。

    public static void main(java.lang.String[]) throws java.lang.Exception;
      descriptor: ([Ljava/lang/String;)V
      flags: ACC_PUBLIC, ACC_STATIC
      Code:
        stack=2, locals=2, args_size=1
           0: invokestatic  #2                  // Method returnsNull:()Ljava/lang/Boolean;
           3: invokevirtual #3                  // Method java/lang/Boolean.booleanValue:()Z
           6: invokestatic  #4                  // Method java/lang/Boolean.valueOf:(Z)Ljava/lang/Boolean;
           9: astore_1
          10: getstatic     #5                  // Field java/lang/System.out:Ljava/io/PrintStream;
          13: aload_1
          14: invokevirtual #6                  // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
          17: return
        LineNumberTable:
          line 3: 0
          line 4: 10
          line 5: 17
      Exceptions:
        throws java.lang.Exception

    public static java.lang.Boolean returnsNull();
      descriptor: ()Ljava/lang/Boolean;
      flags: ACC_PUBLIC, ACC_STATIC
      Code:
        stack=1, locals=0, args_size=0
           0: aconst_null
           1: areturn
        LineNumberTable:
          line 8: 0
于 2016-03-24T01:31:33.823 回答