为什么抛出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中是否有任何引用证实了这种行为,尤其是第二种情况?
答案 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个操作数的类型分别为Boolean
和boolean
,因此本节适用:
如果第二个和第三个操作数之一是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节)的结果。
<special null type>
(请参阅go read 'em!)boolean
<special null type>
(参见§4.1中装箱转换列表中的最后一项)Boolean
因此条件表达式的类型为Boolean
,第3个操作数必须强制为Boolean
。编译器为第3个操作数(false
)插入自动装箱代码。第二个操作数不需要E1
中的自动取消装箱,因此在返回null
时不会自动取消装箱NPE。
这个问题需要类似的类型分析:
答案 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