Generic Inheritance and Subtypes

To explain Generic inheritance, subtypes and wildcards, we use following class hierarchy:

Java Generics Example Class Hierarchy

The Food, Snack and Cookie classes are as below. Fruit and Apple classes are similar.

    public class Food {
        public void foodMethod(){             
        }
    }

    public class Snack extends Food {
        public void snackMethod(){            
        }
    }

    public class Cookie extends Snack {
        public void cookieMethod(){            
        }
    }

We use Java List interface in examples with a focus on its add() and get() methods.

    public interface List<E> {

        public E get(int index);
            
        public void add(E element);
    }

OOP Inheritance

We all know that objects of subclass type can be assigned to variables of superclass type.

    Snack snack = new Snack();
    snack = new Cookie();
    snack = new Food()              // error   

Let’s see how subtype works in the case of generics.

Generic Subtypes

A generic class or interface can have subtype by extending or implementing it. Best example of generic subtype is Collection classes.

Java Generics Collection Rawtype

The ArrayList<E> implements List<E> which inturn extends Collection<E>. Similarly, the HashSet<E> and Set<E>.

As long as type arguments are same, the inheritance is allowed.

Java Generics Collection Subtype
    // list
    ArrayList<Snack> snacks = new ArrayList<>();                
    
    List<Snack> snackList = snacks;
    
    Collection<Snack> snackCollection = snacks;  
    // or
    Collection<Snack> snackCollection = snackList;  

    // set
    HashSet<Snack> snackHashSet = new HashSet<>();                
    
    Set<Snack> snackSet = snackHashSet;
    
    snackCollection = snackHashSet;  
    snackCollection = snackSet;  

As type argument is Snack in all three generic variables, we can assign generic subtypes (ArrayList or List) to its supertypes (Collection) variable. Same way, we can assign HashSet<Snack> or Set<Snack> to Collection<Snack>.

Parameterized Types Compatibility

As explained above, generic subtyping is allowed as long as type argument is same. But, when type arguments are different then rules are stricter. Forget subtyping, even same generic type with different type arguments (parameterized with different type argument) are not compatible with each other. Consider the following

    List<Snack> snacks = new List<>();
    List<Cookie> cookies = snacks;     // error
    List<Food> foods = snacks;         // error

When we try to assign List<Snack> to List<Food> compiler throws error

  • Type mismatch: cannot convert from List<Snack> to List<Food>

This restriction ensures that list of Snack can contain only the Snack objects and nothing else, neither the Cookie (subtype) nor the Food (supertype). Let’s consider another example.

    
    List<Cookie> cookies = new List<>();
    List<Snack> snacks = cookies;              // error

Why List<Cookie> is not assignable to List<Snack>? The original intent with List<Cookie> is that it should hold only the Cookie and nothing else. However, when it is assigned to List<Snack>, Java has to allow it to hold both Cookie and Snack objects which messes the list. Hence, they are not compatible.

Java Generics Collection Subtype

Same is true while calling the methods that has parameters with generic type.

        public void someMethod(List<Snack> s) { }

        List<Snack> snacks = new ArrayList<>();
        List<Cookie> cookie = new ArrayList<>();
        
        someMethod(snack);         
        someMethod(cookie);             // error

As this one of the important concepts of generic, let’s go through one more example to drive home the point.

    
    List<Object> objects = new ArrayList<>();
    List<String> strings = new ArrayList<>();

    objects.add(new Object());              // allowed
    objects.add(new String("hello"));       // allowed

    strings = objects;                      // error

String is subclass of Object, however there is no relationship whatsoever between the two lists - List<Object> and List<String. The List<Object> can hold objects of type Object as well as String (also objects of any Java type as every class is subclass of Object), but we can’t assign list of any other type to it.

We can overcome this restriction with wildcards and in the next tutorial, we see how to use it.

Summary

  • as long as type arguments are same, we can assign generic subtypes to supertypes variable. For example, ArrayList<Snack> is assignable to List<Snack> or Collection<Snack>.
  • when type arguments are different:
    • then inheritance ceases to exist. The ArrayList<Cookie> is not subtype of List<Snack>, even though Cookie is subtype of Snack and ArrayList is subtype of List. The ArrayList<Cookie> is not assignable to List<Snack> or Collection<Snack>.
    • then even generics of same type are not compatible with each other. The List<Cookie> is not subtype of List<Snack>, even though Cookie is subtype of Snack and there is no relationship between these lists. The List<Cookie> is not assignable to List<Snack>.