In Java, List<S>
is not a subtype of List<T>
when S
is a subtype of T
. This rule provides type safety.
Let's say we allow a List<String>
to be a subtype of List<Object>
. Consider the following example:
public void foo(List<Object> objects) {
objects.add(new Integer(42));
}
List<String> strings = new ArrayList<String>();
strings.add("my string");
foo(strings); // this is not allow in java
// now strings has a string and an integer!
// what would happen if we do the following...??
String myString = strings.get(1);
So, forcing this provides type safety but it also has a drawback, it's less flexible. Consider the following example:
class MyCollection<T> {
public void addAll(Collection<T> otherCollection) {
...
}
}
Here you have a collection of T
's, you want to add all items from another collection. You can't call this method with a Collection<S>
for an S
subtype of T
. Ideally, this is ok because you are only adding elements into your collection, you are not modifying the parameter collection.
To fix this, Java provides what they call "wildcards". Wildcards are a way of providing covariance/contravariance. Now consider the following using wildcards:
class MyCollection<T> {
// Now we allow all types S that are a subtype of T
public void addAll(Collection<? extends T> otherCollection) {
...
otherCollection.add(new S()); // ERROR! not allowed (Here S is a subtype of T)
}
}
Now, with wildcards we allow covariance in the type T and we block operations that are not type safe (for example adding an item into the collection). This way we get flexibility and type safety.