Struggling with understanding <? extends T> wildcard in Java
I have a very simple question.
The code below doesn't compile (let's assume Apple extends fruit):
List<? extends Fruit> numbers = new ArrayList<>();
numbers.add(new Apple()); //compile time error
When you read about why not, I understand the words, but not the concept :).
Let's assume Fruit is not an abstract class at first. I understand this as we are dealing with multiple subtypes, all of which extend Fruit. Presumably, since we cannot pinpoint the type of fruit, we cannot put anything into the collection. There are a few things I don't understand:
1) Apparently, we cannot know what fruit he confused me. Wouldn't we be able to tell a specific type with a type check or another instance when iterating through the collection?
2) Assuming Fruit is a concrete class, why not let us add Fruit instances? It looks like it would make sense, because you would know at least the API for fruits. Even if you don't know the exact subtype of Fruit, at least you can refer to standard methods on Fruit ().
I feel like it should be pretty obvious, but something isn't pushing me. Any help is appreciated. Thank!
source to share
The best way to figure this out is to think of a pattern as saying something about a list rather than a 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
in enumerateMyFruit
, inside the method, we lose information about the original declared type of the list. In this example, we can understand very clearly why we shouldn't be able to, for example, put apples in List<? extends Fruit>
, because we know what the list actually is List<Banana>
. Again, the wildcard tells us something about the declared type of the list.
List<? extends Fruit>
should be read as something like "the list originally declared to hold, Fruit
or some subtype Fruit
, but we don't know that the declared type is larger." All we know is that whatever we pull off the list is Fruit
.
Also, you're right, we could iterate over the list and use instanceof
it to find out what is really in the list, but that doesn't tell us the original declared type of the list. In the above code snippet, we learn that everything is in the list Banana
, but I could just as easily declare allBananas
as List<Fruit>
.
You can also see why List<Dog>
notList<Animal>
, which explains some of these. Wildcard is how we have covariance among generic types. a is List<Dog>
not List<Animal>
, but it is a List<? extends Animal>
. This has to do with a constraint that we cannot add to List<? extends Animal>
, because it could be List<Dog>
, a, List<Cat>
or something else. We don't know anymore.
Here also ? super
, which is the opposite . We can store Fruit
in List<? super Fruit>
, but we don't know what objects we will pull out of it. Its initial declared type could actually be, for example, a List<Object>
, with all sorts of other things in it.
source to share
First, remember that for generic options without wildcards, you cannot substitute one for the other. If the method accepts List<Fruit>
, it will not accept a value List<Apple>
, it must be an exact match. Also remember that this is a static type of variable, there is no direct connection to the content. Even if yours List<Fruit>
contains all the Apples, you still cannot replace it with List<Apple>
. Therefore, we are talking about type declarations, not about what is in collections.
Also remember what instanceof
is done at runtime, generic jobs run at compile time. Generics are dedicated to helping the compiler figure out what types are, so you don't need to resort to instanceof and casting.
When the method foo takes a parameter with a generic type List<? extends Fruit>
, it is a way of saying that the method can take on a number of types, in this situation they are any of the following:
-
You can pass
List<Fruit>
-
You can pass
List<Banana>
-
You can pass
List<Apple>
(etc, for whatever subtypes Fruit
you have)
Thus, your method can operate on a list of any of them, however, the body of the method must be valid for any of them. When you add to the list Apple
, it works for the case where the passed value List<Apple>
works for List<Fruit>
, for List<Banana>
not much. (And making fruit concrete doesn't help the case; the addition Fruit
doesn't work for the case List<Apple>
.)
This is why there is a rule that at any time the wildcard type expands something, adding something is not possible, so it cannot work for all possible types that can be passed.
source to share