布尔值,条件运算符和自动装箱

时间:2010-10-07 13:28:52

标签: java nullpointerexception boolean conditional-operator autoboxing

为什么抛出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
}

顺便说一下,解决方案是将false替换为Boolean.FALSE,以避免null被取消装箱到boolean - 这是不可能的。但这不是问题。问题是为什么? JLS中是否有任何引用证实了这种行为,尤其是第二种情况?

4 个答案:

答案 0 :(得分:90)

不同之处在于returnsNull()方法的显式类型会影响编译时表达式的静态类型:

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

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

请参阅Java语言规范,第15.25 Conditional Operator ? :部分

  • 对于E1,第2和第3个操作数的类型分别为Booleanboolean,因此本节适用:

      

    如果第二个和第三个操作数之一是boolean类型,另一个类型是Boolean类型,那么条件表达式的类型是boolean。

    由于表达式的类型为boolean,因此必须将第二个操作数强制转换为boolean。编译器将自动取消装箱代码插入第二个操作数(返回值returnsNull())以使其键入boolean。这当然会导致运行时返回null的NPE。

  • 对于E2,第2和第3个操作数的类型分别为<special null type>(不是Boolean,如E1!)和boolean,因此不适用特定的输入子句({ {3}}),所以最后的“其他”条款适用:

      

    否则,第二和第三操作数分别为S1和S2类型。设T1是将拳击转换应用于S1所产生的类型,让T2为应用到S2的装箱转换所产生的类型。条件表达式的类型是将捕获转换(第5.1.10节)应用于lub(T1,T2)(第15.12.2.7节)的结果。

    • S1 == <special null type>(请参阅go read 'em!
    • S2 == boolean
    • T1 == box(S1)== <special null type>(参见§4.1中装箱转换列表中的最后一项)
    • T2 == box(S2)==`Boolean
    • lub(T1,T2)== Boolean

    因此条件表达式的类型为Boolean,第3个操作数必须强制为Boolean。编译器为第3个操作数(false)插入自动装箱代码。第二个操作数不需要E1中的自动取消装箱,因此在返回null时不会自动取消装箱NPE。


这个问题需要类似的类型分析:

§5.1.7

答案 1 :(得分:24)

该行:

    Boolean b = true ? returnsNull() : false;

内部转换为:

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

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

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

编辑:我认为拆箱是由于第三个运算符是布尔类型,如(隐式强制转换):

   Boolean b = (Boolean) true ? true : false; 

答案 2 :(得分:16)

来自Java Language Specification, section 15.25

  
      
  • 如果是第二个和第三个之一   操作数的类型为boolean和   另一种类型是布尔型,   那么条件的类型   表达式是布尔值。
  •   

因此,第一个示例尝试调用Boolean.booleanValue(),以便根据第一条规则将Boolean转换为boolean

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

  
      
  • 否则,第二和第三   操作数是S1和S2类型   分别。设T1是那种类型   应用拳击的结果   转换为S1,让T2成为   应用拳击造成的类型   转换为S2。的类型   条件表达式就是结果   应用捕获转换   (§5.1.10)至lub(T1,T2)(§15.12.2.7)。
  •   

答案 3 :(得分:0)

我们可以从字节代​​码中看到这个问题。在主要字节代码的第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