努力理解 <?在 Java 中扩展 T> 通配符

Struggling with understanding <? extends T> wildcard in Java

我有一个非常基本的问题。

下面的代码无法编译(假设 Apple Extends Fruit):

    List<? extends Fruit> numbers = new ArrayList<>();
    numbers.add(new Apple());  //compile time error

读 why not 的时候,我看懂了字,但不懂概念:)。

让我们假设 first Fruit 不是抽象的 class。我明白这一点,因为我们正在处理多个子类型,所有这些子类型都扩展了 Fruit。据说因为我们不能说出确切的水果类型,所以我们不能在 collection 中放任何东西。有几件事我不明白:

1) 显然我们不知道是哪种水果让我很困惑。在遍历 collection 时,我们不能通过 typeof 或其他 instanceof 检查来判断具体类型吗?

2) 假设 Fruit 是一个具体的 class,为什么不允许我们添加 Fruit 的实例?这似乎是有道理的,因为您至少会知道 Fruit 的 API。即使您不知道 Fruit 的确切子类型,至少您可以调用 Fruit() 上的标准方法。

我觉得这应该是相当明显的,但有些东西不适合我。任何帮助表示赞赏。谢谢!

理解这一点的最好方法是将通配符视为关于列表而不是水果的说明。换句话说:

List<Banana> allBananas = getMyBananas();
enumerateMyFruit(allBananas);

static void enumerateMyFruit(List<? extends Fruit> myFruit) {
    for (Fruit fruit : myFruit)
        System.out.println(fruit);
}

当我们将 allBananas 传递给 enumerateMyFruit 时,在方法内部我们丢失了有关列表的原始声明类型的信息。在这个例子中,我们可以非常清楚地看到为什么我们不应该能够,例如把苹果放在一个List<? extends Fruit>中,因为我们知道list其实是一个List<Banana>。同样,通配符告诉我们有关列表声明类型的一些信息。

List<? extends Fruit> 应该读作 "a list originally declared to hold Fruit or some subtype of Fruit, but we don't know what that declared type is anymore"。我们所知道的是,我们从列表中拉出的所有内容都是 Fruit.

此外,你是对的,我们可以迭代列表并使用 instanceof 来找出列表中真正的内容,但这不会告诉我们列表的原始声明类型。在上面的代码片段中,我们会发现列表中的所有内容都是 Banana,但我可以像 List<Fruit>.[=37= 一样轻松地声明 allBananas ]


您可能还会看到 why a List<Dog> is not a List<Animal>,其中解释了其中的一些内容。通配符是我们如何在泛型类型之间进行协变的。 List<Dog> 不是 List<Animal>,而是 List<? extends Animal>。这伴随着我们不能添加到 List<? extends Animal> 的限制,因为它可能是 List<Dog>List<Cat> 或其他东西。我们不知道了。

还有 ? superwhich is the opposite。我们可以将 Fruit 存储在 List<? super Fruit> 中,但我们不知道我们将从中取出什么样的对象。它的原始声明类型实际上可能是例如一个 List<Object>,里面还有其他各种东西。

首先请记住,对于没有通配符的泛型参数,您不能用一个代替另一个。如果方法采用 List<Fruit>,则不会采用 List<Apple>,它必须是完全匹配。还要记住这是关于变量的静态类型,与内容没有直接联系。即使您的 List<Fruit> 包含所有苹果,您仍然无法用它代替 List<Apple>。 所以我们讨论的是类型声明,而不是集合中的内容。

还要记住 instanceof 是在运行时完成的,泛型在编译时工作。泛型是关于帮助编译器找出事物的类型,这样您就不必求助于 instanceof 和强制转换。

当方法 foo 采用泛型类型 List<? extends Fruit> 的参数时,这表示该方法可以采用一系列类型,在这种情况下,这些类型是以下任何一种:

  • 你可以传入一个List<Fruit>

  • 你可以传入一个List<Banana>

  • 你可以传入一个List<Apple>

(等等,对于您拥有的 Fruit 的任何子类型)

因此您的方法可以使用其中任何一个的列表,但是,方法主体必须对其中任何一个有效。当您将 Apple 添加到列表时,它适用于传入的是 List<Apple> 的情况,它适用于 List<Fruit>,但对 List<Banana> 则不太适用。 (并且使 Fruit 具体化无济于事, 添加 Fruit 也不适用于 List<Apple> 情况。)

这就是为什么有一条规则,即任何时候通配符类型扩展某些东西时,添加东西是不可能的,它不可能适用于所有可以传入的可能类型。