36

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

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

为什么f1变成null,第二条语句抛出NullPointerException?

Langspec-3.0 第 15.25 段说:

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

所以对于false?1.0f:nullT1 是 Fl​​oat 而 T2 是 null 类型。但是结果是lub(T1,T2)什么?这第 15.12.2.7 段有点过分了......

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

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

4

5 回答 5

26

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

概括

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 内部条件:(false ? 1.0f : null)

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,赋值是对一个 Float 引用变量,所以表达式的结果 (null) 被成功赋值。

对于 f2 外部条件:(false ? 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。

于 2010-04-11T01:03:37.037 回答
4

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

    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;
于 2010-04-11T01:26:31.077 回答
2

当您尝试将 null 分配给原语时,以下将引发 NPE

    float f1 = false ? 1.0f: null;

我相信是导致第二个语句中的 NPE 的原因。因为第一个三元组为 true 返回一个浮点数,所以它也尝试将 false 转换为浮点数。

第一条语句不会转换为 null,因为所需的结果是 Float

例如,这不会抛出 NPE,因为它不再需要转换为原始

    Float f = false? new Float(1.0f): true ? null : 1.0f;
于 2010-04-11T00:28:27.817 回答
2

存在还是不存在,这是个问题。:)

编辑:实际上,仔细观察似乎这种情况实际上是哈姆雷特(三元运算符和包装整数类型)和猫王(自动拆箱空)谜题之间的混合。无论如何,我只能推荐观看视频,它非常有教育意义和乐趣。

于 2010-04-11T01:26:55.380 回答
1

看起来 JVM 试图将第二个 null 拆箱以浮动而不是Float,因此出现 NullPointerException。自己打一次。我的看法是第二个if这样做是因为第一个if的真实部分评估为浮点数,而不是浮点数。

再三考虑之后,我认为这是 Java 告诉您您正在做一些奇怪的事情的一种方式。只是不要嵌套三元ifs,你会没事的:-)

于 2010-04-11T00:22:52.130 回答