【问题标题】:Why doesn't the ternary operator like generic types with bounded wildcards?为什么三元运算符不喜欢有界通配符的泛型类型?
【发布时间】:2014-01-30 16:26:27
【问题描述】:

下面的类定义了两个方法,这两个方法直观地具有相同的功能。每个函数都使用两个 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_45chooseList1 有效,chooseList2 无效。它抱怨:

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

我知道查找包含三元运算符 (… ? … : …) 的表达式类型的规则非常复杂,但据我了解,它会选择第二个和第三个参数都对应的最具体的类型可以在没有显式转换的情况下进行转换。在这里,这应该是List&lt;? super Integer&gt; list1,但不是。

我希望看到为什么不是这种情况的解释,最好是参考 Java 语言规范,并直观地解释如果不是这种情况可能会出现什么问题预防。

【问题讨论】:

  • @BrianRoach 我不认为这是完全重复的,但密切相关。它涉及应用于从泛型方法调用推断返回类型的条件运算符,其中这应用于通配符捕获泛型类型。

标签: java generics ternary-operator bounded-wildcard


【解决方案1】:

此答案适用于 Java 7。

Java 语言规范对conditional operator (? :) 做了以下说明

否则,第二个和第三个操作数的类型为 S1 和 S2 分别。令 T1 为应用拳击产生的类型 转换为 S1,并令 T2 为应用产生的类型 拳击转换为 S2。

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

在表达式中

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

T1List&lt;capture#1? super Integer&gt;T2List&lt;capture#2? super Integer&gt;。这两个都有下界。

This article 详细介绍了如何计算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 类型推断所能做的最好的事情。如果list1List&lt;Object&gt; 并且list2List&lt;Number&gt;,则唯一可接受的返回类型是List&lt;? extends Object&gt;。因为必须涵盖这种情况,所以该方法必须始终返回该类型。

类似

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

lub(T1, T2) 又是List&lt;? extends Object&gt;,其捕获转换为List&lt;capture#XX of ? extends Object&gt;

最后,List&lt;capture#XX of ? extends Object&gt; 类型的引用不能分配给List&lt;? super Integer&gt; 类型的变量,因此编译器不允许这样做。

【讨论】:

  • 我还没弄明白lub()这两种类型是怎么计算出来的。但是为什么List&lt;? extends Object&gt; 是“Java 类型推断能做到的最好的”呢?如果结果是List&lt;? super Integer&gt;,它会更有用,但会有什么复杂性,即在什么情况下会破坏类型安全或使其他事情变得不可能?
  • @Feuermurmel 通过声明? super Integer,您声明了泛型类型的下限。这意味着实际列表可以是List&lt;Integer&gt;List&lt;Number&gt;List&lt;Object&gt; 中的任何一个。 ? : 表达式的类型由两个参数的最小上限决定。它们都是List&lt;? super Integer&gt;,但由于下限,一个可以是List&lt;Number&gt;,另一个可以是List&lt;Object&gt;。 Java 无法在编译时确定这一点,因此结果必须是某种类型,即Object 的子类型,即。 List&lt;? extends Object&gt;.
【解决方案2】:

时间流逝,Java 发生变化。我很高兴地通知您,从 Java 8 开始,可能由于引入了 "target typing",Feuermurmels 示例编译没有问题。

relevant section of the JLS 的当前版本说:

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

...

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

List&lt;String&gt; ls = Arrays.asList();

但这不是:

List&lt;String&gt; 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。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-12-17
    • 1970-01-01
    • 1970-01-01
    • 2014-08-08
    相关资源
    最近更新 更多