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.
animalList. You create it using the stream. The stream's type uses the common type fordogListandcatList, and is thereforeStream<Animal>. You collect it to aList<Animal>. You assign that to theanimalListvariable, but you don't write anything to the list afterwards.List<? extends Animal>"?