Search code examples
javalistpolymorphismextends

Java polymorphism and lists


Say I have the following class structure

public abstract class MyClass{}

public class MyClassA extends MyClass{}

public class MyClassB extends MyClass{}

How do I create a list of MyClass elements that contains both MyClassA and MyClassB elements without objections?

e.g.

    List<MyClass> myList = new LinkedList<MyClassA>();

Solution

  • Depending on your needs you have several options. You can either use plain List<MyClass> type objects. This allows you to add objects of types MyClassA and MyClassB. You can't set a List type object to a variable whose type is List<MyClass>. This is due to a concept called variance. Three types of variance exist in most programming languages that support generics (parametrized polymofism).

    Invariance

    Invariance does not allow one to deviate from a type in either direction. This is the case with generics in Java by default and that's why you can't assign a List<MyClassA> to a variable of List<MyClass>. Imagine the following scenario:

    List<MyClassA> listA = new List<MyClassA>();
    List<MyClass> list = (List<MyClass>) listA; // Produces a compilation error...
    list.add(new MyClassB()); // ... because this would be a big no-no
    

    Covariance

    Covariance allows you to use a the designated parameter type or any subtype thereof. In Java, you use the extends keyword to designate covariance: List<? extends MyClass>. It is allowed to assign a List type object into such a variable. With covariance, however, you can not add any objects to your generic object instance without an explicit cast. That is you can not do the following:

    List<? extends MyClass> list = new LinkedList<MyClassA>();
    list.add(new MyClassA());
    

    This is only logical. After all, you can not know exactly what type of a List you are dealing with. It might as well be a List<MyClassB>.

    Contravariance

    Contravariance is the opposite of covariance and is designated using the super keyword: List<? super MyClassA>. It is allowed to assign a List<MyClass> (but not a List<MyClassB>) type object into such a variable. With contravariance you can add any subtype of the designated type parameter (lower limit in the type hierarchy). That is, it's allowed to do the following:

    List<? super MyClass> list = new LinkedList<MyClass>();
    list.add(new ChildA());
    

    You can not, however, know exactly what type of a List the object is. That is you can only direclty assign an object stored in the list into a variable of type Object without an explicit cast:

    Object o = list.get(0);
    if (o instanceof MyClassA) {
      MyClassA mca = (MyClassA) o;
      //...
    }
    

    I hope this helps to clarify things a bit.