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

Posted

技术标签:

【中文标题】Java条件运算符?:结果类型【英文标题】:Java conditional operator ?: result type 【发布时间】:2011-02-06 14:44:08 【问题描述】:

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

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:null,T1 是 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。

【问题讨论】:

哇 - 15.12.2.7 看起来确实适合睡前阅读......如果有帮助,谷歌搜索表明 lub() 代表“最小上限”。 【参考方案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

详细说明:

这是我通过阅读the spec 并从你得到的结果向后工作的理解。归结为 f2 inner 条件的第三个操作数的类型是 null 类型,而 f2 outer 条件的第三个操作数的类型被认为是 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 inner conditional])

对于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。

【讨论】:

Bert,我试过 System.out.println((false?1.0f:null) instanceof Float) 打印错误,但在阅读 15.20.2 时这一点很清楚。 Java中有什么方法可以检索表达式的类型吗?有趣的是,当您检查表达式 false?1.0f:false?1.0f:null 时,Eclipse 会告诉您它的值为 null。 @Wangnick - 更改为 'System.out.println((true?1.0f:null) instanceof Float)' - 这将返回 'true'。您只需要接受编译时类型与您说的“(false?1.0f:null)”相同。 但是如果你真的想说服自己 - 查看字节码。将代码更改为 'boolean b = false; System.out.println((b?1.0f:null) instanceof Float);'或编译器将在编译时评估 §15.28 常量表达式 '(false?1f:null)'(在字节码中只留下 'aconst_null')。但是新代码应该具有将“1f”(操作数 2)转换为浮点数的字节码 - 因此 (?:) 是浮点数。 @Wangnick - Err... 上面的最后一个 'float' 应该是 'Float' (当编辑计时器)。如果您正在检查字节码(我使用的是 Eclipse),您还可以尝试删除“instanceof”并将“null”更改为“2.0f”并看到字节码 Float cast 消失 - '?:' expr 现在是 float .将“2.0f”改回“null”,Float cast 又回来了。在 Eclipse 中,不要忘记每次都关闭 .class 编辑器窗口,否则您可能看不到字节码的变化。【参考方案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;

【讨论】:

【参考方案3】:

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

    float f1 = false ? 1.0f: null;

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

第一个语句不会转换为 null,因为所需的结果是浮点数

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

    Float f = false? new Float(1.0f): true ? null : 1.0f;

【讨论】:

?: 结果类型转换应该完全独立于后面的赋值,不是吗?请注意,仅打印表达式时会出现同样的问题: System.out.println(false? 1.0f: null); -> null System.out.println(false?1.0f: false?1.0f: null); -> NPE 我知道如何解决这种特殊情况,但我真的很想了解发生了什么,以避免在我的应用程序代码中出现 NPE。 If false?1.0f:null f1 case 工作正常导致 null,为什么它作为 f2 表达式中的第三个值不能正常工作? 因为第一个三元的真实情况是浮点数,它会将其转换为浮点数【参考方案4】:

To be or not to be, that is the question. :)

编辑: 实际上,仔细观察,似乎这种情况实际上是Hamlet(三元运算符和包装整数类型)和Elvis(自动拆箱空)谜题的混合体.无论如何,我只能推荐观看该视频,它很有教育意义,也很有趣。

【讨论】:

这是一个很好的链接:GoogleTechTalks 的“编程语言高级主题:Java 谜题”。它甚至会前进到视频中的特定点。我每天都能学到新东西。 该链接中的谜题确实非常好且具有教育意义!【参考方案5】:

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

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

【讨论】:

好吧,根据我的理解, ?: 结果的类型是预先建立的,并且独立于条件的评估。那么false的类型是什么?1.0f:null,作为F2中的第三个值呢?如果是 null 类型,那么 F2 不应该是 NPE。如果是浮动,则不是,也不是浮动。 如果是这样,那为什么他没有得到编译器错误,并且被允许在运行时看到这个? 是的,类型是预先建立的,但是我们说的是空值,系统要推导出类型。第二个 if 类型是 float,而不是 Float。

以上是关于Java条件运算符?:结果类型的主要内容,如果未能解决你的问题,请参考以下文章

使用Java语言深入理解程序逻辑:条件结构精讲(觉得不行的可以点赞加关注,下次再来评价)

java中的条件运算符是啥?

Java数值类型提升机制(三目条件运算符与空指针问题)

大数据必学Java基础(十八):条件运算符和位运算符

Java控制结构

Java 逻辑运算符