Generics introduced a new kind of type: instantiated generic types.
These are class types (which we have seen before), but now with type arguments, e.g. List<String>.
In Java, subtyping is largely based on the inheritance hierarchy.
With generics, that is still true, and in most cases, the additional requirement is that the type arguments between two types are the same.
For example, in the Java Collections API, there are concrete classes ArrayList<T> and LinkedList<T>, which both
implement the generic list interface List<T>, which in turn extends the generic interface Iterable<T>, which is a supertype of almost all collection interfaces and classes.
Without type arguments, it therefore holds that ArrayList and LinkedList are subtypes of List, and List is a subtype of Iterable (and so are, transitively, ArrayList and LinkedList).
This gets preserved with type arguments if the type arguments are the same.
Therefore:
LinkedList<String> is a subtype of List<String> is a subtype of Iterable<String>
ArrayList<String> is a subtype of List<String> is a subtype of Iterable<String>
LinkedList<Object> is a subtype of List<Object> is a subtype of Iterable<Object>
ArrayList<Object> is a subtype of List<Object> is a subtype of Iterable<Object>
But:
LinkedList<String> is not a subtype of List<Object>
List<String> is not a subtype of Iterable<Object>
ArrayList<Object> is not a subtype of List<String>
List<Object> is not a subtype of Iterable<String>
- ...
The reason for this in principle is Behavioural Subtyping.
Take, for example, LinkedList<String> and List<Object>.
From the outer types, we know that if at all, LinkedList<String> would be a subtype of List<Object>.
This works fine for reading from the list:
//NOTE: not legal Java code
LinkedList<String> strs = new LinkedList<String>();
strs.add("Hello");
List<Object> objs = strs;
Object o = objs.get(0);
Since a LinkedList<String> stores String values, and objs.get is supposed to return an Object,
and every String is an Object, this code is not problematic (though illegal).
The problem here comes in when we write to the list:
//NOTE: not legal Java code
LinkedList<String> strs = new LinkedList<String>();
List<Object> objs = strs;
objs.add(new Person("Fabian", 38));
A List<Object> should be able to contain arbitrary objects, so the call to add with a Person value is fine.
But that would be a problem if the underlying list object is a List<String>, which is not supposed to contain Person values.
The same argument works the other way around (that LinkedList<Object> is not a subtype of List<String>) - this time,
the problem is reading from the list, as one would expect that a List<String> returns a String, but a
LinkedList<Object> cannot guarantee that.