为什么三元运算符不喜欢带有有界通配符的泛型类型?

下面的类定义了两个方法,这两个方法直观地具有相同的功能。每个函数都使用两个类型列表和一个布尔值进行调用,该值指定应将哪些列表分配给局部变量。List<? super Integer>

import java.util.List;

class Example {
    void chooseList1(boolean choice, List<? super Integer> list1, List<? super Integer> list2) {
        List<? super Integer> list;

        if (choice)
            list = list1;
        else
            list = list2;
    }

    void chooseList2(boolean choice, List<? super Integer> list1, List<? super Integer> list2) {
        List<? super Integer> list = choice ? list1 : list2;
    }
}

根据 ,是有效的,而不是。它抱怨:javac 1.7.0_45chooseList1chooseList2

java: incompatible types
  required: java.util.List<? super java.lang.Integer>
  found:    java.util.List<capture#1 of ? extends java.lang.Object>

我知道查找包含三元运算符()的表达式类型的规则非常复杂,但据我所知,它选择了最具体的类型,第二个和第三个参数都可以转换为没有显式强制转换。在这里,这应该是,但事实并非如此。… ? … : …List<? super Integer> list1

我希望看到一个关于为什么情况并非如此的解释,最好是参考Java语言规范,并直观地解释如果不加以阻止,可能会出错。


答案 1

这个答案适用于Java 7。

Java 语言规范对条件运算符 (? : 提出了以下规定:

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

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

在表达式中

List<? super Integer> list = choice ? list1 : list2;

T1是 和 是 。这两者都有下限。List<capture#1? super Integer>T2List<capture#2? super Integer>

本文详细介绍了如何计算 (或 )。让我们从那里举个例子lub(T1, T2)join function

<T> T pick(T a, T b) {
    return null;
}

<C, A extends C, B extends C> C test(A a, B b) {
    return pick(a, b); // inferred type: Object
}

void tryIt(List<? super Integer> list1, List<? super Integer> list2) {
    test(list1,  list2);
}

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

List<? extends Object>

这是 Java 的类型推断可以做到的最好的方法。如果 是 a 并且是 ,则唯一可接受的返回类型是 。由于必须涵盖这种情况,因此该方法必须始终返回该类型。list1List<Object>list2List<Number>List<? extends Object>

类似地在

List<? super Integer> list = choice ? list1 : list2;

再次是,其捕获转换为 。lub(T1, T2)List<? extends Object>List<capture#XX of ? extends Object>

最后,类型的引用不能分配给类型的变量,因此编译器不允许它。List<capture#XX of ? extends Object>List<? super Integer>


答案 2

时间流逝,Java也在变化。我很高兴地通知您,从Java 8开始,可能是由于引入了“目标类型”,Feuermurmels示例编译没有问题。

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

由于引用条件表达式可以是多晶表达式,因此它们可以将上下文“向下传递”到其操作数。

...

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

List<String> ls = Arrays.asList();

但事实并非如此:

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

上述规则允许将两个赋值都视为正确键入。

同样有趣的是,从Sotirios Delimanolis的代码派生的以下内容无法编译:

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

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

我使用jdk_1.8.0_25。