Struggling with understanding <? extends T> wildcard in Java

删除回忆录丶 提交于 2019-12-18 07:06:22

问题


I have a very basic question.

The code below doesn't compile (assume Apple Extends Fruit):

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

When reading about why not, I understand the words but not the concept :).

Let's assume first Fruit is NOT an abstract class. I understand that that since we're dealing with multiple subtypes all of which extend Fruit. Supposedly since we can't tell the exact type of fruit, we can't put anything in the collection. There's a couple things I don't understand:

1) Apparently we cannot know which fruit it is which confused me. Wouldn't we be able to tell the specific type through a typeof or other instanceof check while iterating through the collection?

2) Assuming Fruit is a concrete class, why wouldn't we be allowed to add instances of Fruit? It seems like that would make sense because you would know at minimum the API for Fruit. Even if you don't know the exact subtype of Fruit, at least you can invoke the standard methods on Fruit().

I feel like this should be rather obvious but something isn't clicking for me. Any help is appreciate. Thanks!


回答1:


The best way to understand this is to think of the wildcard as saying something about the list, not the fruit. In other words:

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

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

When we pass allBananas to enumerateMyFruit, inside the method we lose information about the original declared type of the list. In this example we can very clearly see why we shouldn't be able to e.g. put apples in a List<? extends Fruit>, because we know that the list is actually a List<Banana>. Again, the wildcard is telling us something about the declared type of the list.

List<? extends Fruit> should be read as something like "a list originally declared to hold Fruit or some subtype of Fruit, but we don't know what that declared type is anymore". All that we know is that everything we pull out of the list is a Fruit.

Also, you are right, we could iterate the list and use instanceof to find out what is really in the list, but this wouldn't tell us the original declared type of the list. In the above code snippet we would find out that everything in the list turned out to be a Banana, but I could have just as easily declared allBananas as a List<Fruit>.


You might also see why a List<Dog> is not a List<Animal> which explains some of this. The wildcard is how we have covariance among generic types. a List<Dog> is not a List<Animal> but it is a List<? extends Animal>. This comes with the restriction that we can't add to a List<? extends Animal>, because it might be a List<Dog>, a List<Cat> or something else. We don't know anymore.

There's also the ? super, which is the opposite. We can store Fruit in a List<? super Fruit> but we don't know what kinds of objects we will pull out of it. Its original declared type might actually be e.g. a List<Object>, with all kinds of other stuff in it.




回答2:


First remember that for generic parameters without wildcards, you can't substitute one for another. If a method takes a List<Fruit> it won't take a List<Apple>, it has to be an exact match. Also remember this is about the static type of the variable, there's no direct connection to the contents. Even if your List<Fruit> contains all Apples, you still can't substitute it for a List<Apple>. So we're talking about type declarations, not about what's in the collections.

Also remember instanceof is done at runtime, generics work at compile time. Generics are about helping the compiler figure out what types things are so you don't have to resort to instanceof and casting.

When a method foo takes a parameter with the generic type List<? extends Fruit>, that is a way of saying that the method can take a range of types, in this situation those being any of the following:

  • You can pass in a List<Fruit>

  • You can pass in a List<Banana>

  • You can pass in a List<Apple>

(etc., for whatever subtypes of Fruit you have)

So your method can work with a list of any of these, however, the method body has to be valid for any of them. When you add an Apple to the list, that works for the case where what's passed in is a List<Apple>, it works for List<Fruit>, for List<Banana> not so much. (And making Fruit concrete doesn't help matters, adding a Fruit doesn't work for the List<Apple> case either.)

That is why there's a rule that anytime the wildcard type extends something, adding stuff is not possible, there's no way it can work for all the possible types that can be passed in.



来源:https://stackoverflow.com/questions/43421017/struggling-with-understanding-extends-t-wildcard-in-java

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!