Java Generics Capture List <! - ? - >

时间:2012-07-16 07:41:10

标签: java generics

我正在查看Java Generics documentation并找到了这段代码,

public class WildcardError {

void foo(List<?> l) {
    //This give a compile time error
    l.set(0,l.get(0));
}
}

我可以理解,我们正在从List<?>获取元素并尝试将其设置为另一个List<?>。所以编译器会出错。我的问题是,当2个列表不同时才有意义,即l.set(0, m.get(0))此处列表lm不同。但在上面的示例中,ll是相同的列表。为什么编译器不够聪明才能看到它?实施起来难吗?

修改: 我知道我可以通过辅助方法或使用T而不是?来修复它。只是想知道为什么编译器不会为我做这件事。

3 个答案:

答案 0 :(得分:9)

在您的具体情况下,您可以明确解决此问题:

public class WildcardError {
    <T> void foo(List<T> l) {
        // This will work
        l.set(0, l.get(0));
    }
}

或者,如果您不想更改原始API,请引入委托帮助程序方法:

public class WildcardError {
    void foo(List<?> l) {
        foo0(l);
    }

    private <T> void foo0(List<T> l) {
        // This will work
        l.set(0, l.get(0));
    }
}

不幸的是,编译器无法推断出“明显的”<T>类型。我一直在想这个。这似乎可以在编译器中得到改进,因为每张外卡都可以非正式地转换为未知的<T>类型。可能有一些原因可以忽略这一点,也许这只是直觉,但在形式上是不可能的。

<强>更新

请注意,我刚看到Collections.swap()的这种特殊实现:

public static void swap(List<?> list, int i, int j) {
    final List l = list;
    l.set(i, l.set(j, l.get(i)));
}

JDK人员采用原始类型,以便在本地处理此问题。这是一个强有力的声明,表明这可能应该得到编译器的支持,但出于某种原因(例如,没有时间正式指定这一点)只是没有完成

答案 1 :(得分:4)

编译器报告错误,因为通常情况下它无法判断两个表达式(在本例中为ll)是否引用相同的列表。 / p>

相关的,有点概括的问题:

答案 2 :(得分:2)

List<?>表示包含某些未知类型的元素的列表,因此当想要使用list.get(i)从中获取元素时,它将返回某些未知类型的对象,因此唯一有效的猜测是Object。然后当一个人尝试使用list.set(index, list.get(index))设置元素时,它会产生编译时错误,因为如上所述List<?>只能包含一些未知类型,所以放{{1} }它可能导致Object

Joshua Bloch的 Effective Java,第2版,第28项:使用有界通配符来提高API灵活性

这也称为ClassCastException原则,在此问答中可以找到很好的解释: What is PECS (Producer Extends Consumer Super)?(请注意,PECSList<?>相同,但有一些例外情况)

在非专业术语中,人们应该在该方法中仅使用List<? extends Object>作为方法参数来从获取元素,而不是当需要将元素放入名单。当需要 put和get 时,他/她需要使用类型参数List<?>来生成方法,如Lukas Eder的答案(类型安全方式)或仅使用T (不是类型安全的方式)。