布尔,有条件的操作和自动装箱

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

解决方法是通过Boolean.FALSEreplacefalse来避免null被解除boolean是不可能的。 但这不是问题。 问题是为什么 ? JLS中是否有任何证据certificate这种行为,尤其是第二种情况?

不同之处在于,在编译时, returnsNull()方法的显式types会影响expression式的静态types:

 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有条件的运算符?

  • 对于E1,第二个和第三个操作数的types分别是Booleanboolean ,所以本节适用:

    如果第二个和第三个操作数中的一个是布尔types,而另一个的types是布尔types,则条件expression式的types是布尔值。

    由于expression式的types是boolean ,所以第二个操作数必须强制为boolean 。 编译器将自动拆箱代码插入到第二个操作数( returnsNull()返回值)中以使其types为boolean 。 这当然会导致从运行时返回的null NPE。

  • 对于E2,第二个和第三个操作数的<special null type>分别是<special null type> (不像在E1中是Boolean <special null type> )和booleantypes,所以没有特定的分类子句适用( 阅读em! ),所以最后的“otherwise”子句适用:

    否则,第二个和第三个操作数分别是S1和S2的types。 假设T1是将装箱转换为S1所得到的types,并设T2是将装箱转换为S2所得到的types。 条件expression式的types是将采集转换(§5.1.10)应用于lub(T1,T2)(§15.12.2.7)的结果。

    • S1 == <special null type> (见§4.1 )
    • S2 == boolean
    • T1 ==框(S1)== <special null type> (请参阅§5.1.7中的装箱转换列表中的最后一项)
    • T2 ==框(S2)==`布尔值
    • lub(T1,T2)== Boolean

    所以条件expression式的types是Boolean ,第三个操作数必须强制为Boolean 。 编译器为第三个操作数插入自动装箱代码( false )。 第二个操作数不像E1那样需要自动拆箱,因此返回null时不会自动拆箱NPE。


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

Java条件运算符?:结果types

该行:

  Boolean b = true ? returnsNull() : false; 

内部转换为:

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

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

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

编辑:我相信拆箱是由于第三个运算符为布尔types,如(隐式强制添加):

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

从Java语言规范,第15.25节 :

  • 如果第二个和第三个操作数中的一个是布尔types,而另一个的types是布尔types,则条件expression式的types是布尔值。

所以,第一个例子试图调用Boolean.booleanValue()以按照第一条规则将Boolean转换为boolean

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

  • 否则,第二个和第三个操作数分别是S1和S2的types。 假设T1是将装箱转换为S1所得到的types,并设T2是将装箱转换为S2所得到的types。 条件expression式的types是将采集转换(§5.1.10)应用于lub(T1,T2)(§15.12.2.7)的结果。

我们可以从字节码中看到这个问题。 在主字节代码的3: invokevirtual #3 // Method java/lang/Boolean.booleanValue:()Z行, 3: invokevirtual #3 // Method java/lang/Boolean.booleanValue:()Z ,值为null的boxing布尔值, 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