为什么三元运算符不像带有边界通配符的generics类型?

以下类定义了两种方法,两种方法都直观地具有相同的function。 每个函数都使用两个List列表来调用List List和一个布尔值,指定应将哪些列表分配给局部变量。

 import java.util.List; class Example { void chooseList1(boolean choice, List list1, List list2) { List list; if (choice) list = list1; else list = list2; } void chooseList2(boolean choice, List list1, List list2) { List list = choice ? list1 : list2; } } 

根据javac 1.7.0_45chooseList1有效而chooseList2不有效。 它抱怨说:

 java: incompatible types required: java.util.List found: java.util.List 

我知道找到包含三元运算符( … ? … : … )的表达式类型的规则非常复杂,但据我所知,它选择了第二个和第三个参数都可以使用的最具体的类型没有明确的演员表而被转换。 这里应该是List list1 List list1但不是。

我想看看为什么不是这种情况的解释,最好是参考Java语言规范,并直观地解释如果没有阻止可能出错的地方。

这个答案适用于Java 7。

Java语言规范说明了关于条件运算符的以下内容( ? :

否则,第二和第三操作数分别是S1和S2类型。 设T1是将拳击转换应用到S1所产生的类型,让T2为应用到S2的装箱转换所产生的类型。

条件表达式的类型是将捕获转换(第5.1.10节)应用于lub(T1,T2)(第15.12.2.7节)的结果。

在表达中

 List list = choice ? list1 : list2; 

T1List ListT2List List 。 这两者都有下限。

本文详细介绍了如何计算lub(T1, T2) (或join function )。 我们来自那里

  T pick(T a, T b) { return null; }  C test(A a, B b) { return pick(a, b); // inferred type: Object } void tryIt(List list1, List list2) { test(list1, list2); } 

如果您使用IDE并将鼠标hover在test(list1, list2) ,您会注意到返回类型是

 List 

这是Java的类型推断可以做的最好的。 如果list1Listlist2List ,唯一可接受的返回类型是List List 。 因为必须涵盖这种情况,所以该方法必须始终返回该类型。

同样在

 List list = choice ? list1 : list2; 

lub(T1, T2)再次List List和它的捕获转换是List List

最后,类型为List引用List List不能分配给List类型的变量List List所以编译器不允许它。

时间流逝和Java的变化。 我很高兴地通知您,自Java 8以来,可能由于引入了“目标类型” ,Feuermurmels示例编译没有问题。

JLS相关部分的当前版本说:

因为引用条件表达式可以是多重表达式,所以它们可以“向下传递”上下文到它们的操作数。

它还允许使用额外信息来改进generics方法调用的类型检查。 在Java SE 8之前,这个赋值是很好的类型:

List ls = Arrays.asList();

但这不是:

List ls = ... ? Arrays.asList() : Arrays.asList("a","b");

上述规则允许两种分配都被认为是良好类型的。

值得注意的是,以下来自Sotirios Delimanolis的代码不能编译:

 void tryIt(List list1, List list2) { List l1 = list1 == list2 ? list1 : list2; // Works fine List l2 = test(list1, list2); // Error: Type mismatch } 

这表明在计算返回类型的test时类型下限时可用的信息与条件运算符的类型不同。 为什么会出现这种情况我不知道,这可能是一个有趣的问题。

我用的是jdk_1.8.0_25。