Struggling with understanding <? extends T> wildcard in Java
Asked Answered
T

2

7

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!

Tautologism answered 15/4, 2017 at 0:48 Comment(3)
shouldn't it be new Apple() (i.e. the brackets are missing) ?Wraith
possible duplicate of #12293252Lentz
Possible duplicate of Java wildcards and generics ? super T and ? extends TLentz
V
12

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.

Viewable answered 15/4, 2017 at 1:10 Comment(0)
L
3

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.

Lillith answered 15/4, 2017 at 1:22 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.