Categories
generics inheritance java polymorphism

Is List a subclass of List? Why are Java generics not implicitly polymorphic?

863

I’m a bit confused about how Java generics handle inheritance / polymorphism.

Assume the following hierarchy –

Animal (Parent)

DogCat (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?

11

  • 21

    And a totally unrelated grammar question that’s bothering me now – should my title be “why aren’t Java’s generics” or “why isn’t Java’s generics”?? Is “generics” plural because of the s or singular because it’s one entity?

    – froadie

    Apr 30, 2010 at 14:44

  • 27

    generics as done in Java are a very poor form of parametric polymorphism. Don’t put too much into faith into them (like I used to), because one day you’ll hit hard their pathetic limitations: Surgeon extends Handable<Scalpel>, Handable<Sponge> KABOOM! Does not compute [TM]. There’s your Java generics limitation. Any OOA/OOD can be translated fine into Java (and MI can be done very nicely using Java interfaces) but generics just don’t cut it. They’re fine for “collections” and procedural programming that said (which is what most Java programmers do anyway so…).

    Apr 30, 2010 at 15:43

  • 8

    Super class of List<Dog> is not List<Animal> but List<?> (i.e list of unknown type) . Generics erases type information in compiled code. This is done so that code which is using generics(java 5 & above) is compatible with earlier versions of java without generics.

    Dec 4, 2012 at 11:15


  • 5

  • 9

    @froadie since nobody seemed to respond… it should definitely be “why aren’t Java’s generics…”. The other issue is that “generic” is actually an adjective, and so “generics” is referring to a dropped plural noun modified by “generic”. You could say “that function is a generic”, but that would be more cumbersome than saying “that function is generic”. However, it’s a bit cumbersome to say “Java has generic functions and classes”, instead of just “Java has generics”. As someone who wrote their master’s thesis on adjectives, I think you’ve stumbled upon a very interesting question!

    – dantiston

    May 30, 2017 at 5:18

1016

No, a 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 List<Object>.

39

  • 57

    Interestingly, every list of dogs is indeed a list of animals, just like intuition tells us. The point is, that not every list of animals is a list of dogs, hence mutattion of the list by adding a cat is the problem.

    – Ingo

    Jan 28, 2013 at 19:29

  • 75

    @Ingo: No, not really: you can add a cat to a list of animals, but you can’t add a cat to a list of dogs. A list of dogs is only a list of animals if you consider it in a read-only sense.

    – Jon Skeet

    Jan 28, 2013 at 19:33

  • 15

    @JonSkeet – Of course, but who is mandating that making a new list from a cat and a list of dogs actually changes the list of dogs? This is an arbitrary implementation decision in Java. One that goes counter to logic and intuition.

    – Ingo

    Jan 28, 2013 at 19:41

  • 7

    @Ingo: I wouldn’t have used that “certainly” to start with. If you have a list which says at the top “Hotels we might want to go to” and then someone added a swimming pool to it, would you think that valid? No – it’s a list of hotels, which isn’t a list of buildings. And it’s not like I even said “A list of dogs is not a list of animals” – I put it in code terms, in a code font. I really don’t think there’s any ambiguity here. Using subclass would be incorrect anyway – it’s about assignment compatibility, not subclassing.

    – Jon Skeet

    Jan 28, 2013 at 19:58

  • 18

    @ruakh: The problem is that you’re then punting to execution time something which can be blocked at compile-time. And I’d argue that array covariance was a design mistake to start with.

    – Jon Skeet

    Jul 3, 2013 at 17:22

104

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.

0

    49

    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.

    0