Java Generics, Arrays and Method return types - Covariant, Contravariant and Invariant subtyping rules

[Updated: May 8, 2017, Created: May 5, 2017]

First, let's understand generally, what subtyping rules are.

Covariant vs Contravariant vs Bivariant vs Invariant

A programming language can have features which may support the following subtyping rules:

  • Covariant

    A feature which allows to substitute a subtype with supertype.

  • Contravariant

    A feature which allows to substitute a supertype with subtype.

  • Bivariant

    A feature which is both covariant and contravariant.

  • Invariant

    A feature which does not allow any of the above substitution.


Let's see what subtyping rules Java supports:

Java Arrays are Covariant

Java arrays support covariant substitution:

Integer[] integers = new Integer[10];
Number[] numbers = integers;

But above assignment is dangerous because it may end up in ArrayStoreException:

public class CovariantArraysExample {

  public static void main(String[] args) {
      Integer[] integers = new Integer[10];
      Number[] numbers = integers;
      numbers[0] = new Double(25);
  }
}

Output

Caused by: java.lang.ArrayStoreException: java.lang.Double
at com.logicbig.example.CovariantArraysExample.main(CovariantArraysExample.java:8)
... 6 more

Java Generics are invariant

Java Generics with a specific type T, do not support covariant or contravariant substitutions. That's because to avoid the situation like ArrayStoreException which we saw in the case of arrays above.

public class InvariantGenericsExample {

  public static void main(String[] args) {
      List<Integer> integers = new ArrayList<>();
      List<Number> numbers = new ArrayList<>();
      numbers = integers;
  }
}

Output

Compilation errors:
InvariantGenericsExample.java:[11,19] incompatible types: java.util.List<java.lang.Integer> cannot be converted to java.util.List<java.lang.Number>
1 error

Java Generics with wildcard '? extends' are Covariant:

public class CovariantGenericsExample {

  public static void main(String[] args) {
      List<Integer> integers = new ArrayList<>();
      integers.add(1);

      List<? extends Number> numbers = integers;
      System.out.println(numbers);
  }
}

Output

[1]

Note that after assignment to List<? extends Number>, we cannot add any subtype of Number to the List. For example attempts like this will end up in compilation error:

public class CovariantGenericsExample2 {

  public static void main(String[] args) {
      List<Integer> integers = new ArrayList<>();
      integers.add(1);

      List<? extends Number> numbers = integers;
      Double d = new Double(23);
      numbers.add(d);
  }
}

Output

Compilation errors:
CovariantGenericsExample2.java:[14,16] no suitable method found for add(java.lang.Double)
method java.util.Collection.add(capture#1 of ? extends java.lang.Number) is not applicable
(argument mismatch; java.lang.Double cannot be converted to capture#1 of ? extends java.lang.Number)
method java.util.List.add(capture#1 of ? extends java.lang.Number) is not applicable
(argument mismatch; java.lang.Double cannot be converted to capture#1 of ? extends java.lang.Number)
1 error

The reason for the compilation error is; we cannot guarantee what type of elements the List originally constructed for. '? extends Number' says any type which extends Number, but compiler can never know what type it is. We can only add null to such collection reference or we can remove items from it but we cannot add new elements to it.

Java Generics with wildcard '? super' are Contravariant:

Java Generics allows to substitute a supertype with subtype with '? super' wildcards:

public class ContravariantGenericsExample {

    public static void main(String[] args) {
        List<A> aList = new ArrayList<>();
        aList.add(new A());

        List<? super B> bList = aList;
        bList.add(new B());

        System.out.println(bList);
    }

    private static class A {
    }

    private static class B extends A {
    }
}

Output

[com.logicbig.example.ContravariantGenericsExample$A@436e27a6, com.logicbig.example.ContravariantGenericsExample$B@69c3928e]

Note that in above case, we were able to add an element of type B after the assignment. That's because, the compiler knows that bList is a List of any type that is a supertype of B and for sure B (or subtypes of B) will always be assignable to a super type of B.

On the other hand, from '? super B', the compiler will never know which supertype of B it is, that means we cannot add any element of type A or other subtypes of A to bList.

Covariant method return types in Java

Java allows a subclass to override a method which can return a more specific type. In other words, the return type of the subclass method must be substitutable with the return type of the corresponding method of the super class. Please check out our last tutorial for example.


See Also