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

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

?

解决方案是顺便替换,以避免被拆箱到 - 这是不可能的。但这不是问题所在。问题是为什么?JLS中是否有任何参考资料证实了这种行为,特别是第二种情况?falseBoolean.FALSEnullboolean


答案 1

不同之处在于 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 节条件运算符 ? :

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

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

    由于表达式的类型是 ,第 2 个操作数必须强制为 。编译器将自动取消装箱代码插入到第 2 个操作数(的返回值)中,使其键入 。这当然会导致 NPE 在运行时返回。booleanbooleanreturnsNull()booleannull

  • 对于 E2,第 2 和第 3 个操作数的类型分别是(不像 E1 中那样!),因此没有特定的类型子句适用(去读 'em!),所以最后的“否则”子句适用:<special null type>Booleanboolean

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

    • S1 == (参见 §4.1<special null type>)
    • S2 ==boolean
    • T1 == box(S1) == (请参阅 §5.1.7 中拳击转换列表中的最后一项<special null type>)
    • T2 == box(S2) == 'Boolean
    • 润滑油(T1, T2) ==Boolean

    因此,条件表达式的类型是,并且必须强制为 第 3 个操作数。编译器为第三个操作数 () 插入自动装箱代码。第 2 个操作数不需要像 中那样自动开箱,因此在返回时不会自动开箱 NPE。BooleanBooleanfalseE1null


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

Java 条件运算符 ?: 结果类型


答案 2

该行:

    Boolean b = true ? returnsNull() : false;

在内部转换为:

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

执行拆箱;因此:将产生 NPEnull.booleanValue()

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

编辑:我相信取消装箱是由于第三个运算符是布尔类型,如(隐式强制转换添加):

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