Java中装箱类型的三目运算符表现奇怪

23

我在我的应用程序中有这段代码(简化版):

Object result;
if (check)
    result = new Integer(1);
else
    result = new Double(1.0);
System.out.println(result);
return result;

然后我决定将if-else语句重构为三元条件表达式,以使我的代码更加简洁:

然后我决定将if-else语句重构为三元条件表达式,以使我的代码更加简洁:

Object result = check ? new Integer(1) : new Double(1.0);
System.out.println(result);
return result;

原来,如果检查为true,则这两个版本将打印出不同的结果:

1

或:
1.0

三元条件运算符是否等同于相应的if-else语句?


5
由于您提供的代码,描述的情况是不可能发生的,因此您的代码中出现了一些不同的情况。 - JamesB
@JamesB 抱歉,我更新了我的问题。把1.0和1交换了。 - fkn
1
@JamesB 我可以重现这个问题 O_o - Stefan Falk
@JamesB check是一个函数的参数。 - fkn
我不知道为什么这个问题会被经常点赞,考虑到 https://dev59.com/c18e5IYBdhLWcg3w6d4_ 和 https://dev59.com/7Gsz5IYBdhLWcg3wR1vC and https://dev59.com/2m7Xa4cB1Zd3GeqPoEl1 ... - Marco13
5个回答

25

if/else语句和条件(三元)表达式并不完全等价。条件表达式的结果必须具有一种类型。

您正在观察数字类型提升(或类型强制转换)的效果。

以下是语言规范的摘录(请参见此处),该摘录来自于描述条件表达式返回值的部分:

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

最后一种情况是(我在此省略了其他情况):

否则,将应用二进制数字提升(§5.6.2)到操作数类型,并且条件表达式的类型是第二个和第三个操作数的提升类型。

以下是与二进制数字提升相关的附加规范:

按照以下规则,扩展原始类型转换(§5.1.2)被应用于将一个或两个操作数转换为指定类型:

  • 如果任一操作数的类型为double,则另一个操作数将被转换为double。

这是第一种情况(省略了其他情况)。double总是胜出。

因此,无论条件表达式中第二个和第三个操作数的顺序如何,表达式的返回类型都将提升为double


我认为你真的需要深入了解二进制数值提升规则,才能完全理解发生了什么。它可以执行 int 和 double 的拆箱转换,然后将 int 扩展为 double。就像你说 new Integer(1) + new Double(1.0) 一样。 - Mike Zboray

6
简单来说,三元运算符在数值类型提升方面与其他运算符没有区别。当你使用类似 System.out.println(1 + 1.0) 这样的语句时,你期望它会打印出 2.0,因���输出中使用的操作数是经过数值类型提升的。三元运算符也是完全相同的:在执行数值类型提升后,System.out.println(true ? 1 : 1.0) 将打印出 1.0,就像表达式 1 + 1.0 一样。此方法的原因非常简单:运算符结果的类型应该在编译时就已知,而实际结果是在运行时确定的。

4

简短回答

第一个例子显式地将类型声明为Object,导致向上转型。

第二个例子隐式地将类型声明为Double,导致数值扩展。


详细回答

在使用Object的示例中,没有对值进行转换,只是进行了向上转型,输出结果为1。

Object result;
if (1 == 1)
    result = new Integer(1);
else
    result = new Double(1.0);

如果您使用 Double 声明,那么它将被扩展并打印 1.0。
Double result;
if (1 == 1)
    result = new Integer(1);
else
    result = new Double(1.0);

这些都相当直接,因为有一个明确的类型。
然而,三元表达式没有明确的类型,规则非常复杂。
条件表达式的类型确定如下:
- 如果第二个和第三个操作数具有相同的类型(可以是null类型),那么这就是条件表达式的类型。 - 如果第二个和第三个操作数中有一个是基本类型T,而另一个的类型是应用了装箱转换(§5.1.7)到T的结果,则条件表达式的类型是T。 - 如果第二个和第三个操作数中有一个是null类型,而另一个的类型是引用类型,则条件表达式的类型是该引用类型。 - 否则,如果第二个和第三个操作数具有可转换(§5.1.8)为数值类型的类型,则有几种情况: - 如果其中一个操作数的类型是byte或Byte,另一个操作数的类型是short或Short,则条件表达式的类型是short。 - 如果其中一个操作数的类型为T,其中T是byte、short或char,另一个操作数是int类型的常量表达式(§15.28),其值在类型T中是可表示的,则条件表达式的类型是T。 - 如果其中一个操作数的类型为T,其中T是Byte、Short或Character,另一个操作数是int类型的常量表达式(§15.28),其值在应用拆箱转换到T的结果类型U中是可表示的,则条件表达式的类型是U。 - 否则,将二元数值提升(§5.6.2)应用于操作数类型,并且条件表达式的类型是第二个和第三个操作数的提升类型。请注意,二进制数值提升执行值集转换(§5.1.13),并且可能执行拆箱转换(§5.1.8)。 - 否则,第二个和第三个操作数分别为S1和S2类型。让T1是应用装箱转换到S1的结果类型,T2是应用装箱转换到S2的结果类型。条件表达式的类型是对lub(T1,T2)(§15.12.2.7)应用捕获转换(§5.1.10)的结果。
来源:http://docs.oracle.com/javase/specs/jls/se7/html/jls-15.html#jls-15.25

"Integer"和"Double"这两种数字类型的“提升类型”是"Double"。


2
除了 @pb2q 的回答之外,您还可以通过以下方式进行验证:
public class test {
    public static void main(String[] args) {
    Object result;
    Boolean check = true;

        if (check)
            result = new Integer(1);
        else
            result = new Double(1.0);
        System.out.println(result);
        result = check ? new Integer(2) : new Double(1.0);
        System.out.println(result);
    }
}

由于数字提升,它将打印2.0而不是2。


这不就是问题中的代码吗? - Radiodef
1
这是与之前不同的地方:result = check ? new Integer(2) : new Double(1.0); 它将打印出 2.0 而不是 2。 - sparsh610
2
那对于问答有什么贡献呢? - Radiodef
它之所以显示为1.0,是因为使用了new Integer(1)而不是new Double(1.0) - sparsh610
我明白你的意思:结果是由于数字提升,而不是因为某种原因评估条件为“false”。(也许你应该把这个加到你的答案里?) - Radiodef

1

除了现有的答案,您还可以通过特定的类型转换来避免问题:

Object result = args.length < 100 ? (Object)2 : (Object)1.0;

将整数转换为Object会将其封装为Integer,将双精度浮点数转换为Double。 ":"两侧的表达式现在都是Object类型,因此编译器不需要生成任何其他转换。

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