代码对象 o = true ? new Integer(0) : new Long(1) 返回值为 Long 类型且值为 0。为什么?

10
请考虑我们有以下代码:
Object obj = true ? new Integer(0) : new Long(1);
System.out.println(obj.getClass() + "\nvalue = " + obj);

它的结果是:

class java.lang.Long
value = 0

改为:

class java.lang.Integer
value = 0

有人能否澄清一下为什么Java中会有这样的功能?这对我来说非常奇怪。您是否有任何例子可以说明这可能有用?

更新: 这里是一段字节码,我们可以看到正在发生什么。

NEW java/lang/Integer
DUP
LDC "0"
INVOKESPECIAL java/lang/Integer.<init> (Ljava/lang/String;)V
INVOKEVIRTUAL java/lang/Integer.intValue ()I
I2L
INVOKESTATIC java/lang/Long.valueOf (J)Ljava/lang/Long;
ASTORE 1

要使三元语句有效,两侧应返回相同类型的值。 - Andrew Tobilko
2
@PeterLawrey Object o = false ? new Long(0) : new Integer(1); 会返回 class java.lang.Long1。它不是最后一个参数的类型,而是可以通过扩展接收的类型。 - QBrute
2
我喜欢那些研究不周的问题获得许多赞,但是现在要求阅读JLS太过分了... - Tom
2
澄清一下,这是 为什么三元运算符会意外地转换整数? 的副本,而不是其他的问题。 - Tunaki
2
请参见 https://dev59.com/c18e5IYBdhLWcg3w6d4_ 和略微相关的 https://dev59.com/b2sz5IYBdhLWcg3wHUW0。 - Tunaki
4个回答

12
这里发生的情况是:
  • 二进制数字提升将您的IntegerLong类型转换为long,以用作条件运算符表达式适用的通用类型
  • 取消装箱这些包装器对象
  • 然后打包条件表达式的结果值

条件运算符的第二个和第三个操作数必须具有相同的类型,这是表达式的结果类型。当然,IntegerLong不是相同的类型。

然而,如JLS§15.25所述,编译器将在确定要应用于表达式的可能通用类型时应用二进制数字提升。该部分具有方便的表格15.25-D,告诉我们当第二个操作数为Integer类型并且第三个操作数为Long类型时,编译器将对Integer,Long进行二进制数字提升。在Integer,Long上执行二进制数字提升得到long。因此,条件运算符表达式的结果为long

由于表达式的结果类型为long,所以IntegerLong将会被取消装箱(并在Integer的情况下进行转换)。

最后,将其赋值给Object,这将强制打包,并用Long包装long。因此,您最终得到一个包含值0Long,它与输出匹配。

因此,实际上,如果我们忽略编译器将优化以下代码的一半,因为它正在处理常量表达式,感谢代码中的true(我在下面使用了flag),则该代码最终变成:

Object obj = Long.valueOf(flag ? (long)(new Integer(0)).intValue() : (new Long(1)).longValue());
System.out.println(obj.getClass() + "\nvalue = " + obj);
  • (long)(new Integer(0)).intValue() 表示将 Integer 拆箱并强制转换为 long,使其与表达式的结果类型匹配。
  • (new Long(1)).longValue() 表示将 Long 拆箱,使其与表达式的结果类型匹配。
  • Long.valueOf 表示在最后进行装箱操作。

4
这种行为在 JLS - 15.25. 三元运算符 ? : 中有很好的解释:
条件运算符有三个操作数表达式。问号 ? 出现在第一个和第二个表达式之间,冒号 : 出现在第二个和第三个表达式之间。
条件表达式的类型如下确定:
- 如果第二个和第三个操作数都是布尔类型,则条件表达式的类型为 boolean。 - 否则,如果第二个和第三个操作数具有可转换(§5.1.8)为数字类型的类型,则有几种情况: - [...] - 否则,将对操作数类型进行二进制数字提升(§5.6.2),并且条件表达式的类型是第二个和第三个操作数的提升类型。

0

这与三元运算符的工作原理有关。

冒号:两侧的操作数必须是兼容的类型,因此您不能这样做:

true ? 1 : "Hello"

由于intString不能隐式地相互转换。

但在您的情况下,这些类型是兼容的!一个int可以隐式转换为long。这就是编译器所做的!它看到了一个int和一个long,并决定表达式应该评估为一个long。它取消封箱两个值并隐式将int转换为long。最后,它将结果long装箱,使其成为Long并将其放入变量中。


0
实际上,long 可以存储整数的值,但整数不能存储 long 的值(概念是扩展),你将其存储在对象中,所以它在 Long 中存储。内部还使用装箱和拆箱。
可能的编译器代码:
Long obj = true ? new Integer(0) : new Long(1);

System.out.println(obj.getClass() + "\nvalue = " + obj);

无法正常工作的代码(无法编译):

 Integer obj = true ? new Integer(0) : new Long(1);

System.out.println(obj.getClass() + "\nvalue = " + obj);

自己检查一下,如果有疑问请告诉我。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接