Is List<Dog> a subclass of List<Animal>? Why aren't Java's generics implicitly polymorphic?

87
June 19, 2017, at 02:37 AM

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?

Answer 1

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>.

Answer 2

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.

Answer 3

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.

Answer 4

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];
  objects[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.

Answer 5

A point I think should be added to what other answers mention is that while

List<Dog> isn't-a List<Animal> in Java

it is also true that

A list of dogs is-a list of animals in English (well, under a reasonable interpretation)

The way the OP's intuition works - which is completely valid of course - is the latter sentence. However, if we apply this intuition we get a language that is not Java-esque in its type system: Suppose our language does allow adding a cat to our list of dogs. What would that mean? It would mean that the list ceases to be a list of dogs, and remains merely a list of animals. And a list of mammals, and a list of quadrapeds.

More generally, OP's intuition lends itself towards a language in which operations on objects can change their type, or rather, an object's type(s) is a (dynamic) function of its value.

Answer 6

The basis logic for such behavior is that Generics follow a mechanism of type erasure. So at run time you have no way if identifying the type of collection unlike arrays where there is no such erasure process. So coming back to your question...

So suppose there is a method as given below:

add(List<Animal>){//You can add List<Dog or List<Cat> and this will compile as per rules of polymorphism}

Now if java allows caller to add List of type Animal to this method then you might add wrong thing into collection and at run time too it will run due to type erasure. While in case of arrays you will get a run time exception for such scenarios...

Thus in essence this behavior is implemented so that one cannot add wrong thing into collection. Now I believe type erasure exists so as to give compatibility with legacy java without generics....

Answer 7

The answers given here didn't fully convince me. So instead, I make another example.

public void passOn(Consumer<Animal> consumer, Supplier<Animal> supplier) {
    consumer.accept(supplier.get());
}

sounds fine, doesn't it? But you can only pass Consumers and Suppliers for Animals. If you have a Mammal consumer, but a Duck supplier, they should not fit although both are animals. In order to disallow this, additional restrictions have been added.

Instead of the above, we have to define relationships between the types we use.

E. g.,

public <A extends Animal> void passOn(Consumer<A> consumer, Supplier<? extends A> supplier) {
    consumer.accept(supplier.get());
}

makes sure that we can only use a supplier which provides us the right type of object for the consumer.

OTOH, we could as well do

public <A extends Animal> void passOn(Consumer<? super A> consumer, Supplier<A> supplier) {
    consumer.accept(supplier.get());
}

where we go the other way: we define the type of the Supplier and restrict that it can be put into the Consumer.

We even can do

public <A extends Animal> void passOn(Consumer<? super A> consumer, Supplier<? extends A> supplier) {
    consumer.accept(supplier.get());
}

where, having the intuitive relations Life -> Animal -> Mammal -> Dog, Cat etc., we could even put a Mammal into a Life consumer, but not a String into a Life consumer.

Answer 8

Actually you can use an interface to achieve what you want.

public interface Animal {
    String getName();
    String getVoice();
}
public class Dog implements Animal{
    @Override 
    String getName(){return "Dog";}
    @Override
    String getVoice(){return "woof!";}

}

you can then use the collections using

List <Animal> animalGroup = new ArrayList<Animal>();
animalGroup.add(new Dog());
Answer 9

If you are sure that the list items are subclasses of that given super type you can cast the list using this approach:

(List<Animal>) (List<?>) dogs

This is usefull when you want to pass the list in a constructor or iterate over it

Answer 10

The answer https://stackoverflow.com/a/2745301/4350148 as well as other answers are correct. I am going to add to those answers with a solution that I think will be helpful. I think this comes up often in programming. One thing to note, is that for Collections(Lists,Sets, etc) the main issue is adding to the Collection. That is where things break down. Even removing is OK. In most cases we can use Collection<? extends T> rather then Collection<T> and that should be the first choice. However, I am finding cases where it is not easy to do that. It is up for debate as to whether that is always the best thing to do. I am presenting here a class DownCastCollection that can take convert a Collection<? extends T> to a Collection<T> (we can define similar classes for List, Set, NavigableSet,..) to be used when using the standard approach is very inconvenient. Below is an example of how to use it (we could also use Collection<? extends Object> in this case, but I am keeping it simple to illustrate using DownCastCollection.

/**Could use Collection<? extends Object> and that is the better choice. 
* But I am doing this to illustrate how to use DownCastCollection. **/
public static void print(Collection<Object> col){  
    for(Object obj : col){
    System.out.println(obj);
    }
}
public static void main(String[] args){
  ArrayList<String> list = new ArrayList<>();
  list.addAll(Arrays.asList("a","b","c"));
  print(new DownCastCollection<Object>(list));
}

Now the class:

import java.util.AbstractCollection;
import java.util.Collection;
import java.util.Iterator;
import java.util.NoSuchElementException;
public class DownCastCollection<E> extends AbstractCollection<E> implements Collection<E> {
private Collection<? extends E> delegate;
public DownCastCollection(Collection<? extends E> delegate) {
    super();
    this.delegate = delegate;
}
@Override
public int size() {
    return delegate ==null ? 0 : delegate.size();
}
@Override
public boolean isEmpty() {
    return delegate==null || delegate.isEmpty();
}
@Override
public boolean contains(Object o) {
    if(isEmpty()) return false;
    return delegate.contains(o);
}
private class MyIterator implements Iterator<E>{
    Iterator<? extends E> delegateIterator;
    protected MyIterator() {
        super();
        this.delegateIterator = delegate == null ? null :delegate.iterator();
    }
    @Override
    public boolean hasNext() {
        return delegateIterator != null && delegateIterator.hasNext();
    }
    @Override
    public  E next() {
        if(!hasNext()) throw new NoSuchElementException("The iterator is empty");
        return delegateIterator.next();
    }
    @Override
    public void remove() {
        delegateIterator.remove();
    }
}
@Override
public Iterator<E> iterator() {
    return new MyIterator();
}

@Override
public boolean add(E e) {
    throw new UnsupportedOperationException();
}
@Override
public boolean remove(Object o) {
    if(delegate == null) return false;
    return delegate.remove(o);
}
@Override
public boolean containsAll(Collection<?> c) {
    if(delegate==null) return false;
    return delegate.containsAll(c);
}
@Override
public boolean addAll(Collection<? extends E> c) {
    throw new UnsupportedOperationException();
}
@Override
public boolean removeAll(Collection<?> c) {
    if(delegate == null) return false;
    return delegate.removeAll(c);
}
@Override
public boolean retainAll(Collection<?> c) {
    if(delegate == null) return false;
    return delegate.retainAll(c);
}
@Override
public void clear() {
    if(delegate == null) return;
        delegate.clear();
}

}

Answer 11

Lets take the example from JavaSE tutorial

public abstract class Shape {
    public abstract void draw(Canvas c);
}
public class Circle extends Shape {
    private int x, y, radius;
    public void draw(Canvas c) {
        ...
    }
}
public class Rectangle extends Shape {
    private int x, y, width, height;
    public void draw(Canvas c) {
        ...
    }
}

So why a list of dogs (circles) should not be considered implicitly a list of animals (shapes) is because of this situation:

// drawAll method call
drawAll(circleList);

public void drawAll(List<Shape> shapes) {
   shapes.add(new Rectangle());    
}

So Java "architects" had 2 options which address this problem:

  1. do not consider that a subtype is implicitly it's supertype, and give a compile error, like it happens now

  2. consider the subtype to be it's supertype and restrict at compile the "add" method (so in the drawAll method, if a list of circles, subtype of shape, would be passed, the compiler should detected that and restrict you with compile error into doing that).

For obvious reasons, that chose the first way.

Answer 12

I have same problem. But generic type support <? extends Y> to fix this problem:

Collection<A> aCollection= getAFromDB(); AUtil.findMetConidition(aCollection);

Class:

public interface A extends B {...}

Method declaration:

b findMetConidition(Collection<? extends  B> bCollection ) { ...}
READ ALSO
I want to update my TextView in android

I want to update my TextView in android

Hi i hope that you can help mI'm using RecycleView and want to update my textview with the number of row of my table in sqlite db after each insert on it

109
What is the proper way to load SubType&#39;s into ArrayList of type of BaseType with sqlite select?

What is the proper way to load SubType's into ArrayList of type of BaseType with sqlite select?

I have a table of BaseType which have the column Type for SubType objects

83
who is responsible for offset maintenance?

who is responsible for offset maintenance?

Here are the Kafka docs for public ConsumerRecords<K,V> poll(long timeout)

112
Saving and Reading an ArrayList

Saving and Reading an ArrayList

Saving method(save list is an Arraylist)-

86