理解这一点的最好方法是将通配符认为是关于列表的某件事,而不是水果。换一种说法:
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<? extendsFruit>应该读为“最初声明要保留的列表
Fruit或的子类型
Fruit,但我们不知道该声明类型是什么”。我们所知道的是,我们从列表中拉出的所有内容都是一个
Fruit。
另外,您是对的,我们可以迭代列表并使用它
instanceof来查找列表中实际包含的内容,但这并不能告诉我们列表的原始声明类型。在上面的代码片段中,我们发现列表中的所有内容都是a
Banana,但是我可以很容易地将其声明
allBananas为a
List<Fruit>。
您可能还会看到为什么a
List<Dog>不是a
List<Animal>的原因。通配符是我们如何在泛型类型之间进行协方差。a
List<Dog>不是a,
List<Animal>而是a
List<? extends Animal>。这带有我们不能添加到的限制
List<?extends Animal>,因为它可能是
List<Dog>,a
List<Cat>或其他。我们不知道了。
另外还有
?super,这是相反的。我们可以将其存储
Fruit在中,
List<?super Fruit>但我们不知道要从中取出哪种对象。它的原始声明类型实际上可能是例如
List<Object>,其中包含各种其他内容。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)