我正在查看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))
此处列表l
和m
不同。但在上面的示例中,l
和l
是相同的列表。为什么编译器不够聪明才能看到它?实施起来难吗?
修改:
我知道我可以通过辅助方法或使用T
而不是?
来修复它。只是想知道为什么编译器不会为我做这件事。
答案 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)
编译器报告错误,因为通常情况下它无法判断两个表达式(在本例中为l
和l
)是否引用相同的列表。 / 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)?(请注意,PECS
与List<?>
相同,但有一些例外情况)
在非专业术语中,人们应该在该方法中仅使用List<? extends Object>
作为方法参数来从获取元素,而不是当需要将元素放入名单。当需要 put和get 时,他/她需要使用类型参数List<?>
来生成方法,如Lukas Eder的答案(类型安全方式)或仅使用T
(不是类型安全的方式)。