Home > Back-end >  Java Generics in Stream
Java Generics in Stream

Time:02-21

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.

  • Related