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!
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With