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

我对条件运算符有点困惑。请考虑以下两行:

Float f1 = false? 1.0f: null;
Float f2 = false? 1.0f: false? 1.0f: null;

为什么 f1 变为 null,而第二个语句会引发 NullPointerException?

Langspec-3.0 para 15.25 sais:

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

因此,对于 T1 是 Float,T2 是空类型。但是结果是什么?这个段落15.12.2.7只是有点太多了...false?1.0f:nulllub(T1,T2)

顺便说一句,我在Windows上使用1.6.0_18。

PS:我知道这不会抛出NPE。Float f2 = false? (Float) 1.0f: false? (Float) 1.0f: null;


答案 1

不同之处在于编译时表达式的静态类型:

总结

E1: `(false ? 1.0f : null)`
    - arg 2 '1.0f'           : type float,
    - arg 3 'null'           : type null 
    - therefore operator ?:  : type Float (see explanation below)
    - therefore autobox arg2
    - therefore autobox arg3

E2: `(false ? 1.0f : (false ? 1.0f : null))`
    - arg 2 '1.0f'                    : type float
    - arg 3 '(false ? 1.0f : null)'   : type Float (this expr is same as E1)
    - therefore, outer operator ?:    : type float (see explanation below)
    - therefore un-autobox arg3

详细说明:

这是我从阅读规范并从你得到的结果向后工作的理解。它归结为 f2 内部条件的第三个操作数的类型为 null 类型,而 f2 外部条件的第三个操作数的类型被视为 Float。

注意:重要的是要记住,类型的确定和装箱/拆箱代码的插入是在编译时完成的。装箱/取消装箱代码的实际执行是在运行时完成的。

Float f1 = (false ? 1.0f : null);
Float f2 = (false ? 1.0f : (false ? 1.0f : null));

f1 条件和 f2 内部条件:(假 ? 1.0f :空)

f1 条件和 f2 内部条件是相同的:(false ? 1.0f : null)。f1 条件和 f2 内部条件中的操作数类型为:

type of second operand = float
type of third operand = null type (§4.1)

§15.25中的大多数规则都被放弃了,并且确实应用了此最终评估:

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

S1 = float
S2 = null type
T1 = Float
T2 = null type
type of the f1 and f2 inner conditional expressions = Float

由于对于 f1,赋值是浮点型引用变量,因此表达式的结果 (null) 被成功赋值。

对于 f2 外部条件:(假 ? 1.0f : [f2 内部条件])

对于 f2 外部条件,类型为:

type of second operand = float
type of third operand = Float

请注意,与直接引用 null 文本的 f1/f2 内部条件相比,操作数类型存在差异 (§4.1)。由于具有 2 个数字可转换类型的这种差异,因此 §15.12.2.7 中的此规则适用:

  • 否则,如果第二个和第三个操作数具有可转换为数值类型的类型 (§5.1.8),则有以下几种情况:...

    • 否则,二进制数值提升 (§5.6.2) 将应用于操作数类型,条件表达式的类型是第二和第三个操作数的升级类型。请注意,二进制数字升级会执行拆箱转换§5.1.8) 和值集转换 (§5.1.13)。

由于对 f2 内部条件 (null) 的结果执行了取消装箱转换,因此会引发 NullPointerException。


答案 2

我认为重写代码会使解释更清晰:

    float f = 1.0f;

    Float null_Float  = false?        f  : null;       // float + null  -> OK
    Float null_Float2 = false? (Float)f  : null_Float; // Float + Float -> OK
    Float npe         = false?        f  : null_Float; // float + Float -> NPE

因此,NPE是当我们尝试做这样的事情时:

Float npe = false? 1.0f : (Float)null;