苦苦挣扎<! - ?在Java中扩展T - > wildcard

时间:2017-04-15 00:48:03

标签: java wildcard extends

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

下面的代码没有编译(假设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()上调用标准方法。

我觉得这应该是相当明显的,但有些东西不能为我点击。任何帮助都很感激。谢谢!

2 个答案:

答案 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>或其他内容。我们不知道了。

还有? superwhich 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>案例。)

这就是为什么有一条规则,即只要通配符类型扩展了某些内容,就无法添加内容,因此无法为所有可以传入的类型工作。