我有一个非常基本的问题。
下面的代码无法编译(假设 Apple Extends Fruit):
List<? extends Fruit> numbers = new ArrayList<>();
numbers.add(new Apple()); //compile time error
当阅读“为什么不”时,我理解单词,但不理解概念:)。
首先我们假设 Fruit 不是一个抽象类。我明白,因为我们正在处理多个子类型,所有这些子类型都扩展了 Fruit。据说由于我们无法说出水果的确切类型,因此我们无法将任何内容放入集合中。有几件事我不明白:
1)显然我们不知道这是什么水果,这让我很困惑。在迭代集合时,我们是否能够通过 typeof 或其他 instanceof 检查来判断特定类型?
2) 假设 Fruit 是一个具体类,为什么我们不允许添加 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<Banana>
。同样,通配符告诉我们一些有关列表声明类型的信息。
List<? extends Fruit>
应该被理解为“最初声明为保存 Fruit
或 Fruit
的某些子类型的列表,但我们不再知道声明的类型是什么”。我们所知道的是,我们从列表中取出的所有内容都是 Fruit
.
另外,你是对的,我们可以迭代列表并使用 instanceof
找出列表中真正的内容,但这不会告诉我们列表的原始声明类型。在上面的代码片段中,我们会发现列表中的所有内容都是 Banana
,但我可以轻松声明 allBananas
作为List<Fruit>
.
您可能还会看到why a List<Dog>
is not a List<Animal>
这解释了其中的一些原因。通配符是我们在泛型类型之间实现协变的方式。一个List<Dog>
不是List<Animal>
但它是一个List<? extends Animal>
。这带有我们无法添加到 List<? extends Animal>
的限制。 ,因为它可能是 List<Dog>
,一个List<Cat>
或者是其他东西。我们已经不知道了。
还有? super
, which is the opposite 。我们可以存储Fruit
在 List<? super Fruit>
但我们不知道我们会从中取出什么样的物体。它最初声明的类型实际上可能是例如一个List<Object>
,其中还有各种其他东西。
关于java - 难以理解 <? Java 中扩展 T> 通配符,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57103279/