布尔,有条件的操作和自动装箱
为什么这会抛出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.FALSE
replacefalse
来避免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分别是
Boolean
和boolean
,所以本节适用:如果第二个和第三个操作数中的一个是布尔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>
)和boolean
types,所以没有特定的分类子句适用( 阅读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。 - S1 ==
这个问题需要类似的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