三元运算符如何评估结果数据类型?

时间:2015-05-29 16:11:44

标签: java type-conversion ternary-operator

鉴于这段代码

public class Main {

    public static void main(String[] args) {
        foo(1);
        foo("1");
        foo(true?1:"1");
        foo(false?1:"1");
    }

    static void foo(int i){System.out.println("int");}
    static void foo(String s){System.out.println("String");}
    static void foo(Object o){System.out.println("Object");}
}

这是我得到的输出:

int
String
Object
Object

我无法理解为什么在最后两个案例中foo(Object o)被调用,而不是foo(int i)foo(String s)。 不是运行时评估的三元表达式的返回类型吗?

修改

让我感到困惑的是断言

System.out.println((y>5) ? 21 : "Zebra");

编译因为( OCA学习指南 - Sybex ):

  

System.out.println()并不关心语句是完全不同的类型,因为它可以将两者转换为 String

虽然重点是 println 被重载以接受 Object 类型作为输入。相当误导,imho。

3 个答案:

答案 0 :(得分:10)

三元的两种替代方案必须属于同一类型。 Integer和String唯一的常见类型是Object,因此操作数都被强制转换为Object,而三元组的类型在 compile 时确定为Object。

然后,编译器使用Object参数静态绑定到该方法。

重要的不是逻辑上三元的结果在编译时是可以确定的 - 编译器不会那样工作。它处理表达式和类型。它必须首先解析三元表达式的类型,而不是,为此,它必须首先找到操作数的公共类型。

答案 1 :(得分:7)

  

不是在运行时评估三元表达式的返回类型吗?

不,绝对没有。这与Java工作的方式非常相反,其中重载解析等总是在编译时执行。如果您没有将结果传递给方法,但是尝试将其分配给变量,您会发生什么?你会宣布什么样的变量?

表达式的类型受JLS 15.25规则的约束。在您的两种情况下,第三个操作数的类型为String,导致这是引用条件表达式,因此应用了表15.25-E,结果为lub(Integer,Object)lub部分引用JLS 4.10.4,这是相当令人困惑的 - 此处的类型 Object完全相同,但在大多数情况下它可以被认为是这样。

答案 2 :(得分:0)

我想,除了其他答案之外,这是。 (或者,特别是对于任何想要理解迂腐答案的人。)

参考类型的三元条件的结果最终为lub(trueType, falseType),所以在这种特殊情况下,它是lub(Integer, String)lub is

  

一组引用类型的最小上限或" lub"是一个共享超类型,它比任何其他共享超类型更具体[...]。

为了理解lub,我们可以做一些"刮擦"简单类型的算法版本如下。

首先,为每种类型制作一个表格。在一列中,记下超类层次结构,在另一列中,记下所有已实现的接口:

+------------------------+------------------------+
|      Integer           |       String           |
+------------------------+---------+--------------+
| classes |  interfaces  | classes |  interfaces  |
+------------------------+---------+--------------+
| Object  | Serializable | Object  | Serializable |
| Number  | Comparable   | String  | Comparable   |
| Integer |              |         | CharSequence |
+---------+--------------+---------+--------------+

(实际算法会将类和接口整合在一起,只是"超类型"。)

现在:

  • 从底部开始删除所有超类,直到遇到常见的超类。
  • 删除所有未共享的接口。
+------------------------+------------------------+
|      Integer           |       String           |
+------------------------+---------+--------------+
| classes |  interfaces  | classes |  interfaces  |
+------------------------+---------+--------------+
| Object  | Serializable | Object  | Serializable |
| Number  | Comparable   | String  | Comparable   |
| Integer |              |         | CharSequence |
+---------+--------------+---------+--------------+

lub现在是该公共超类和共享接口给出的交集类型:

lub(Integer, String) =
    Object & Serializable & Comparable

由于lub,此处Comparable稍微复杂一点。从技术上讲,lub(Integer, String)产生无限类型,因为我们需要再次(无限地)为lub(Integer, String)的类型参数执行Comparable

lub(Integer, String) =
    Object & Serializable & Comparable<? extends lub(Integer, String)>

条件(boolean ? Integer : String)的类型是将捕获转换应用于lub(Integer, String)(yikes!)的结果类型。

foo(Object)重载被选中,因为它最适用于此类型。

其他一些有趣的事情:

  • 如果我们添加了重载foo(Serializable),则会选择它,因为SerializableObject更具体。
  • 如果我们添加了两个重载foo(Serializable)foo(Comparable<?>),我们就会出现歧义错误。