我有一个非常基本的问题。
下面的代码没有编译(假设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()上调用标准方法。
我觉得这应该是相当明显的,但有些东西不能为我点击。任何帮助都很感激。谢谢!
答案 0 :(得分:8)
理解这一点的最好方法是将通配符看作是对列表的一些说法,而不是结果。换句话说:
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>
,其中包含各种其他内容。
答案 1 :(得分:3)
首先请记住,对于没有通配符的通用参数,您不能将其替换为另一个。如果某个方法需要List<Fruit>
,它就不会使用List<Apple>
,那么它必须是完全匹配的。还记得这是关于变量的静态类型,没有与内容的直接连接。即使您的List<Fruit>
包含所有苹果,您仍然无法将其替换为List<Apple>
。
所以我们讨论的是类型声明,而不是集合中的内容。
还记得instanceof
在运行时完成,泛型在编译时工作。泛型是关于帮助编译器弄清楚事物的类型,因此你不必诉诸于instanceof和cast。
当方法foo采用泛型类型List<? extends Fruit>
的参数时,这是一种表示该方法可以采用一系列类型的方式,在这种情况下,这些是以下任何一种:
您可以传递List<Fruit>
您可以传递List<Banana>
您可以传递List<Apple>
(等等,对于你所拥有的Fruit
的任何子类型)
因此,您的方法可以使用其中任何一个的列表,但是,方法体必须对其中任何一个都有效。当您向列表中添加Apple
时,适用于传入的内容为List<Apple>
的情况,适用于List<Fruit>
,List<Banana>
不适用非常。 (而制作水果混凝土并不会有帮助,
添加Fruit
也不适用于List<Apple>
案例。)
这就是为什么有一条规则,即只要通配符类型扩展了某些内容,就无法添加内容,因此无法为所有可以传入的类型工作。