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 是 Float,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 这样做是因为第一个 if 的 true 部分评估为浮点数,而不是浮点数。
再三考虑之后,我认为这是 Java 告诉您您正在做一些奇怪的事情的一种方式。只是不要嵌套三元ifs,你会没事的:-)
【讨论】:
好吧,根据我的理解, ?: 结果的类型是预先建立的,并且独立于条件的评估。那么false的类型是什么?1.0f:null,作为F2中的第三个值呢?如果是 null 类型,那么 F2 不应该是 NPE。如果是浮动,则不是,也不是浮动。 如果是这样,那为什么他没有得到编译器错误,并且被允许在运行时看到这个? 是的,类型是预先建立的,但是我们说的是空值,系统要推导出类型。第二个 if 类型是 float,而不是 Float。以上是关于Java条件运算符?:结果类型的主要内容,如果未能解决你的问题,请参考以下文章