Java泛型List<List<? extends Number>>

39

在Java中,为什么我们不能这样做:

List<List<? extends Number>> aList = new ArrayList<List<Number>>();

尽管这样没问题:

List<? extends Number> aList = new ArrayList<Number>();

编译器错误消息如下:

类型不匹配: 无法将ArrayList<List<Number>>转换为List<List<? extends Number>>

5个回答

74

在Java中,如果CarVehicle的派生类,那么我们可以将所有的Car视为Vehicle; 一个Car是一个Vehicle然而,List<Car>不是List<Vehicle>。我们说List<Car>List<Vehicle>之间不具备协变性(non-covariant)

当使用通配符?时,Java要求您明确告诉它何时希望使用协变性和逆变性。看一下问题发生的位置:

List<List<? extends Number>> l = new ArrayList<List<Number>>();
//        ----------------                          ------
// 
// "? extends Number" matched by "Number". Success!

内部的List<? extends Number>之所以可行,是因为Number确实扩展了Number,因此它与"? extends Number"匹配。 目前为止,一切正常。接下来呢?

List<List<? extends Number>> l = new ArrayList<List<Number>>();
//   ----------------------                    ------------
// 
// "List<? extends Number>" not matched by "List<Number>". These are
//   different types and covariance is not specified with a wildcard.
//   Failure.

然而,组合内部类型参数 List<? extends Number>List<Number> 不匹配;这些类型必须是完全相同。另一个通配符将告诉Java该组合类型也应该是协变的:

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

似乎在左侧使用 List<List<? extends Number>> 更能符合发布者的意图。 - erickson
@erickson 但是 List<List<? extends Number>> 给出了编译错误 incompatible types: ArrayList<List<Number>> cannot be converted to List<List<? extends Number>>。这对我来说很反直觉,但是当 RHS 是 new ArrayList<List<...>>(); 时,不能在 LHS 上使用 List<List...>>。多级通配符会使事情变得棘手。请参见 What is the difference between <? extends Base> and <T extends Base>? 以获取最近相关问题的答案。 - skomisa

3

在适当的情况下,您应该绝对使用 ? 类型通配符,不要将其作为一般规则避免使用。例如:

public void doThingWithList(List<List<? extends Number>> list);

允许您传递List<Integer>List<Long>

public void doThingWithList(List<List<Number>> list);

允许您只传递声明为List<Number>的参数。是一个微小但功能强大且安全的区别。与表面上看起来的不同,List<Integer>不是List<Number>的子类,也不可从中分配。List<Integer>也不是List<? extends Number的子类,这就是为什么上面的代码无法编译的原因。


3

2
您的语句无法编译,因为List<? extends Number>List<Number>不是相同类型。前者是后者的超类型。
您尝试过这个吗?我在这里表达的是List在其类型参数中是协变的,因此它将接受List<? extends Number>的任何子类型(包括List<Number>)。
List<? extends List<? extends Number>> aList = new ArrayList<List<Number>>();

或者甚至是这样。右侧ArrayList的类型参数与左侧的类型参数相同,因此方差不是一个问题。

List<List<? extends Number>> aList = new ArrayList<List<? extends Number>>();

你应该能够直接说

List<List<Number>> aList = new ArrayList<List<Number>>();

我倾向于尽可能避免使用类型通配符。我发现类型注释所带来的开销不值得这样做。


这两个建议都可以解决问题,但仍然无法解释原始语句不起作用的事实。 - coder

-2
List<List<? extends Number>> aList = new ArrayList<List<? extends Number>>();
aList.add(new ArrayList<Integer>());

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