为什么泛型类型不能同时适用于参数的 extends 和 super 类型限定?

13

以下是我一直试图寻找解决方案的问题。

我们有两个类定义。其中一个继承了另一个。

    class T{}
    class TT extends T{}

要求是有一个列表,该列表中的对象都继承自T。
    List<? extends T> list = new ArrayList<>();

但是当我尝试将一个TT对象(似乎它是T的子类)放入列表中时,问题就出现了。
    list.add(new TT());

编译错误信息

在类型 List 中,方法 add(capture#2-of ? extends Cell) 对于参数 (Cell) 不可用。


5个回答

11

您可以直接创建一个List<T> list = new ArrayList<T>();,这样可以将所有T的子类型放入列表中。实际上,这有点难以理解。当您将其声明为

List<? extends T> list = ...

这意味着它可以允许任何未知的T子类型进入列表。但是,从该声明中我们无法确保T的确切子类型是什么。因此,我们只能将null添加到其中。


9

List<? extends T> 表示从中取出的任何内容都可以转换为 T,因此真正的列表可以是以下任何一种:

  • List<T>
  • List<T2>
  • List<T3>
  • 等等

您可以看到,即使新的 T 也不能安全地添加到这样的集合中,因为它可能是一个 List<T2>,而 T 无法放入其中。因此,这样的 List<? extends T> 不能添加非空条目。

在这种情况下,您可能只需要 List<T>

那么为什么要使用这个?!

这种逆变性对于方法参数或返回值可能很有用,在其中将读取集合,而不是添加到集合中。这种用法可以创建一个接受任何包含 T 或扩展 T 的项的集合的方法。

public static void processList(Collection<? extends Vector3d> list){
    for(Vector3d vector:list){
        //do something
    }
}

这个方法可以接受任何扩展Vector3d的对象集合,因此ArrayList<MyExtendedVector3d>是可接受的。

同样,一个方法也可以返回这样的集合。一个使用案例在从指定返回Collection<ParentType>的方法中返回Collection<ChildType>中描述。


是否可能实例化一个类型参数包含有界通配符的 List 呢?例如,List<? extends T> list = new ArrayList<? extends T>()? 我猜不行,因为我会收到编译器错误,但当像 OP 的例子一样使用菱形操作符时会发生什么呢? - Kevin Bowersox
@KevinBowersox 使用 <> 可以编译通过,但这完全是无意义的事情。 - Richard Tingle

7
要求需要一个列表,其中包含扩展自T的对象。
如果您只想要一个可以存储任何扩展自T类的对象的列表,则可以创建以下List:
List<T> list = new ArrayList<T>();

当前您创建的列表只允许添加null,不能添加其他内容。


2
@ᴍarounᴍaroun 我试图在我的回答中解释,但由于列表不能保证接受任何对象类型而是null,这就是问题所在。 - Richard Tingle
@ᴍarounᴍaroun 啊!抱歉我刚离开一下,但我猜你已经得到答案了。 - Rohit Jain
@codelovesme 我无法完全理解你的评论。你能否稍微改进一下语法?否则,我猜你已经明白了要点。 - Rohit Jain

3

在使用通配符时,Java泛型定义了边界规则。

  **extends Wildcard Boundary**

列表意味着一组由类T的实例或子类(如TT)构成的对象列表。这意味着读取操作是可以的,但插入操作将失败,因为您不知道该类是否已被类型化为T。

**super Wildcard Boundary**

当您知道列表的类型为T或T的超类时,可以安全地将T的实例或T的子类(例如TT)插入列表中。

在您的示例中,应使用“super”。


0
除了其他答案中提到的内容,我想补充一点,我只在方法参数和返回类型中使用通配符。它们是用于方法签名而不是实现的。当我将通配符放入变量声明中时,我总是会遇到麻烦。

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