1

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?

3
  • 5
    You're not writing anything to animalList. You create it using the stream. The stream's type uses the common type for dogList and catList, and is therefore Stream<Animal>. You collect it to a List<Animal>. You assign that to the animalList variable, but you don't write anything to the list afterwards. Commented Feb 20, 2022 at 16:06
  • Are you doubting the statement that "you can't write to List<? extends Animal>"? Commented Feb 20, 2022 at 16:19
  • @Sweeper Right, that's exactly what I'm asking and confused about. Commented Feb 21, 2022 at 1:58

1 Answer 1

3

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.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.