views:

236

answers:

4

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

Assume the following hierarchy -

Animal (Parent)

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 subset 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?

+15  A: 

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 List<Dog>();
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>.

Jon Skeet
Wow. Nice and concise. Thanks, starting to make sense to me now, instead of being an arbitrary Java rule.
froadie
+4  A: 

What you are looking for is called covariant type parameters. The problem is that they are not type-safe in the general case, specifically for mutable lists. Suppose you have a List<Dog>, and it is allowed to function 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 therefore 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.

Michael E
+4  A: 

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.

Michael Aaron Safyan
+1  A: 

I would say the whole point of Generics is that it doesn't allow that. Consider the situation with arrays, which do allow that type of covariance:

  Object[] objects = new String[10];
  object[0] = Boolean.FALSE;

That code compiles fine, but throws a runtime error. It is not typesafe. The point of Generics is to add the compile time type safety, otherwise you could just stick with a plain class without generics.

Now there are times where you need to be more flexible and that is what the ? super Class and ? extends Class are for. The former is when you need to insert into a type Collection (for example), and the latter is for when you need to read from it, in a type safe manner. But the only way to do both at the same time is to have a specific type.

Yishai
Arguably, array covariance is a language design bug. Note that due to type erasure, the same behaviour is technically impossible for generic collection.
Michael Borgwardt