I’m a bit confused about how Java generics handle inheritance / polymorphism.
Assume the following hierarchy –
Dog – Cat (Children)
So suppose I have a method
doSomething(List<Animal> animals). By all the rules of inheritance and polymorphism, I would assume that a
List<Dog> is a
List<Animal> and a
List<Cat> is a
List<Animal> – and so either one could be passed to this method. Not so. If I want to achieve this behavior, I have to explicitly tell the method to accept a list of any subclass of Animal by saying
doSomething(List<? extends Animal> animals).
I understand that this is Java’s behavior. My question is why? Why is polymorphism generally implicit, but when it comes to generics it must be specified?
List<Dog> is not a
List<Animal>. Consider what you can do with a
List<Animal> – you can add any animal to it… including a cat. Now, can you logically add a cat to a litter of puppies? Absolutely not.
// Illegal code - because otherwise life would be Bad List<Dog> dogs = new ArrayList<Dog>(); // ArrayList implements List List<Animal> animals = dogs; // Awooga awooga animals.add(new Cat()); Dog dog = dogs.get(0); // This should be safe, right?
Suddenly you have a very confused cat.
Now, you can’t add a
Cat to a
List<? extends Animal> because you don’t know it’s a
List<Cat>. You can retrieve a value and know that it will be an
Animal, but you can’t add arbitrary animals. The reverse is true for
List<? super Animal> – in that case you can add an
Animal to it safely, but you don’t know anything about what might be retrieved from it, because it could be a
What you are looking for is called covariant type parameters. This means that if one type of object can be substituted for another in a method (for instance,
Animal can be replaced with
Dog), the same applies to expressions using those objects (so
List<Animal> could be replaced with
List<Dog>). The problem is that covariance is not safe for mutable lists in general. Suppose you have a
List<Dog>, and it is being used as a
List<Animal>. What happens when you try to add a Cat to this
List<Animal> which is really a
List<Dog>? Automatically allowing type parameters to be covariant breaks the type system.
It would be useful to add syntax to allow type parameters to be specified as covariant, which avoids the
? extends Foo in method declarations, but that does add additional complexity.
The reason a
List<Dog> is not a
List<Animal>, is that, for example, you can insert a
Cat into a
List<Animal>, but not into a
List<Dog>… you can use wildcards to make generics more extensible where possible; for example, reading from a
List<Dog> is the similar to reading from a
List<Animal> — but not writing.
The Generics in the Java Language and the Section on Generics from the Java Tutorials have a very good, in-depth explanation as to why some things are or are not polymorphic or permitted with generics.