Java泛型,嵌套通配符集合

34

这个代码可编译通过(1.6)

List<? extends Object> l = new ArrayList<Date>();

但是这并不会改变原来的意思。

List<List<? extends Object>> ll = new ArrayList<List<Date>>();

随着错误的出现

Type mismatch: cannot convert from ArrayList<List<Date>> to List<List<? extends Object>>

有人能解释一下为什么吗?谢谢。

编辑:修改以保持一致。

5个回答

24

嗯,这些解释是正确的,但我认为将实际的可行方案添加进来会是一件好事 ;)

List<? extends List<? extends Object>>

这样做是完全可行的,但很显然这种集合的使用受到通用集合的常规限制的严重限制(但对于更简单的 List< ? extends Date > 也是如此)。


15

因为这会破坏类型安全性:

List<List<Object>> lo = new ArrayList<List<Object>>();
List<List<? extends Object>> ll = lo;
List<String> ls = new ArrayList<String>();
ll.add(ls);
lo.get(0).add(new Object());
String s = ls.get(0); // assigns a plain Object instance to a String reference

人们可能想要了解C# 4.0中添加的通用接口和委托的协变/逆变声明,这是“解决”此问题的一种方法(尽管它首先改变了问题)。 - Voo
3
@Voo看到一篇非常古老的论文《对于参数类型的基于方差的子类型划分》。文章分析了两种方法的优缺点。Java选择了一种方法,C#选择了另一种方法,但并没有什么新东西。正如论文所述,C#的方法在简单情况下似乎很简单,但复杂性很容易变得难以管理。(我不赞赏C#的人把它呈现为一种没有缺陷、神奇的发明) - irreputable
@irreputable 如果我有时间,我会看一下这篇论文,然后我们可以谈一些关于协变/逆变的高深话题 ;) 而且泛型本身就很复杂,但是有时候能够表达协变/逆变是很有用的。但是这确实不是给心脏脆弱的人准备的。但是Java一开始就没有选择 - 我不知道如何向后兼容地实现它。但是我认为每个人都同意Java的实现方式很糟糕,所以真的没有好主意在其上添加任何东西。 - Voo
lo.get(0).add(new Object()); 会引发错误,因为您试图将一个对象添加到字符串列表中。 - clapas
@Voo Scala 也是这样做的。 - Rag

3
假设 DB 的子类型,G<T> 是一种泛型类型。
B x = new D(); // OK

G<B> y = new G<D>(); // FAIL

现在,G<Date>G<?>的子类型,因此。
G<?> x = new G<Date>();  // OK

G<G<?>> y = new G<G<Date>>(); // FAIL

2
当给一个具有非通配符泛型类型T的变量(List)分配值时,被分配的对象必须恰好具有T作为其泛型类型(包括T的所有泛型类型参数,通配符和非通配符)。在您的情况下,T是List,它与List不是相同的类型。
因为List可以分配给List,所以您可以使用通配符类型:
List<? extends List<? extends Object>> a = new ArrayList<List<Date>>();

0
<? extends Object>  

这意味着通配符只能替换那些是Object类的子类的对象。

List<List<? extends Object>> ll = new ArrayList<List<Object>>();  

你收到了类型不匹配的错误,因为你正在尝试将一个包含Java类Object的对象列表ArrayList分配给一个包含任何Java类Object子类对象列表List

如需更多参考,请查看通配符文档


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接