5

我正在查看Java 泛型文档并找到了这段代码,

public class WildcardError {

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

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

编辑:我知道我可以通过辅助方法或使用T代替?. 只是想知道为什么编译器不为我做。

4

3 回答 3

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 家伙求助于原始类型,以便在本地处理这个问题。这是一个强有力的声明,表明编译器可能应该支持这一点,但由于某种原因(例如,没有时间正式指定这一点)只是没有完成

于 2012-07-16T07:45:21.250 回答
4

编译器报告一个错误,因为一般来说,它无法判断两个表达式(在本例中为land l)是否引用同一个列表。

相关的,有点笼统的问题:

于 2012-07-16T07:43:39.503 回答
3

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

这在 Joshua Bloch 的Effective Java,第 2 版,第 28 条:使用有界通配符提高 API 灵活性中得到了很好的解释

这也称为PECS原理,可以在此 Q/A 中找到很好的解释: 什么是 PECS(Producer Extends Consumer Super)?(请注意,与少数例外情况List<?>相同)List<? extends Object>

用外行的话来说,一个人应该List<?>只使用作为方法参数来从该方法中获取元素,而不是在需要将元素放入列表时。当一个人既需要put 又需要 get时,他/她需要T像 Lukas Eder 的回答(类型安全方式)中那样使用类型参数来泛化方法,或者简单地使用List<Object>(非类型安全方式)。

于 2012-07-16T07:52:38.080 回答