I know that you use <? extends> wildcard when you only get values out of a collection.
Suppose there's Animal superclass and Dog and Cat subclasses. Basically I want to have a list that contains dogs and cats. I found out I can do the following:
List<? extends Animal> animalList;
List<Dog> dogList = getDogs();
List<Cat> catList = getCats();
animalList = Stream.concat(dogList.stream(), catList.stream()).collect(Collectors.toList())
// Do things according to its type
for (Animal animal : animalList) {
if (animal instance of Dog) {...}
if (animal instance of Cat) {...}
}
The above code compiles. So does it violate the rule in the sense that I'm writing values to animalList
?
CodePudding user response:
Stream.concat(dogList.stream(), catList.stream()).collect(Collectors.toList())
This creates a List<Animal>
. Crucially, not a List<? extends Animal>
. Try it:
List<Animal> = ... all that ...
works fine.
List<Animal>
doesn't mean that everything in it was made using literally new Animal()
. You can have a List<Animal>
that contains solely Cat
instances. Those are all animals, that's fine.
The 'point' of ? extends
and all that is when you deal with the lists themselves, not with the things within them. Here's specifically why:
List<Animal> x = new ArrayList<Cat>();
x.add(new Dog());
Cat z = x.get(0);
Triple check the above code but it explains exactly why ? extends
(and ? super
) exists. Generics must be invariant, anything else leads to broken code. The above code must not compile because it makes no sense. As written, it indeed doesn't compile - line 1 isn't allowed. You can 'make it work' by writing List<? extends Animal> x = new ArrayList<Cat>()
which compiles fine, but now x.add(new Dog()
won't.
The difference is this:
a List<Animal>
variable is pointing at some list that is actually some list of specifically <Animal>
and not some subtype or supertype of Animal. It might be a LinkedList<Animal>
or an ArrayList<Animal>
, that's fine, but not an ArrayList<Cat>
. With that known, when you 'read' from it, you get Animal objects out, and when you write to it, Animal is fine.
a List<? extends Animal>
variable on the other hand is some list that is of Animal or some subtype of Animal. It could be a LinkedList<Dog>
. Given that fact, when you read, Animal
is fine (Animal f = thatList.get(0)
compiles fine), but you can't write anything to it. It might be a list of Dogs, but it could also be a list of Cats, and absolutely no object is therefore save (Except, trivially, literally the expression null
, written out like that: thatList.add(null)
compiles. And isn't useful, of course).
You assign your List<Animal>
expression to a variable of type List<? extends Animal>
which is fine. And needless; List<Animal> x = Stream.concat....
would have worked just as well.