Before we get into the Co/Contra-variants we should quikcly go thorugh sub and super types.
public class Bird {}
public class Duck extends Bird {}
In this case, Bird
is a superclass of Duck
. While Duck
is a subclass of Bird
. In UML this is designated by an arrow pointing from Duck
to Bird
.
Covariant
Covariant is when the ordering of types is preserved. It orders types from more specific to more generic.
Enumerable<Cat>
is a subtype ofEnumerable<Animal>
. The sub-typing is preservedEnumerable
is covariant on<T>
So you're covariant on something. In this case we're covariant on <T>
.
An example where your covariant is an array in Java. This is because String[]
is a subtype of Object[]
.
What you can do
Covariance means that you're able to get out the object from the covariant structure. Lets take this in an array.
void extract(List[] arr) {
List list = arr[0];
Object ob = arr[0];
}
So due to arrays being covariant you're able to extract values. This is because anything passed in must be a subtype of List[] arr
. Therefore we at least have a List[] arr
. We may have been passed in anything that is a subtype of List
as well.
Therefore we can get out a List
and any super-type of List
with guarantees that it will not fail.
What you cannot do
Covariance has its problems. Lets take a peek
public static void main(String[] args) {
String[] names = {"Paul", "Kirren", "Bianca"};
Object[] casted = (Object[]) names;
// This is NOT a syntax error
casted[0] = 1;
}
In this case we've successfully down casted to an Object[]
. This is valid because Object[]
is the superclass of String[]
.
The problem occurs when you attempt to insert into the array. Any object at all is allowed into an Object[]
array. If the base array was of type Object[]
that would be fine! Unfortunately in our case it's of type String[]
and thus fails with:
Exception in thread "main" java.lang.ArrayStoreException: java.lang.Integer
Contravariant
Contravariant if the order is reversed from Covariant.
Action<Animal>
is a subtype ofAction<Cat>
The sub-typing is reversed because Action<T>
is contra-variant on T
.
import java.util.ArrayList;
import java.util.List;
public class Test {
public static void main(String[] args) {
take(new ArrayList<Object>());
take(new ArrayList<Integer>());
// This is a SYNTAX error, as it's not a superclass of Integer
take(new ArrayList<Extension>());
// So is this, it's totally unrelated to Integer
take(new ArrayList<String>());
}
public static void take(List<? super Integer> list) {
// I know that the BASE list is of type Integer, or any Super class
// So what do we get out of this?
// We are guaranteed to be able to Insert integers!
list.add(new Integer(5));
// Note that this does not allow us to insert anything in the super chain
list.add(new Object()); // This is invalid. We could have a List<Integer>
}
private static class Extension extends Integer {
public Extension(int num) {
super(num);
}
}
}
What we can do!
Remember the previous example, we weren't able to insert because an Object[]
may hide the fact the base array is Integer[]
. In this case though, take
will only accept Lists that will allow Integer
to be inserted. You can easily prove this, as the only superclass of Integer
is Object
. Thus, this can be provided a List<Object>
or a List<Integer>
.
What we can't do
Now our problem lies in the opposite. With covariance
you're always assured that you're able to extract the type in the variable. Contravariance does not provide us with that same guarantee, in fact, getting a variable from a contravariant structure provides no guarantees and can fail.
In this case, imagine take
was provided with a List<Object>
, you would be unable to take an Integer
from it.
So contravariance allows for insertion, but not extraction. This because you know that the container provided knows how to store at least one type in the hierarchy, therefore, your object.
In Summary
Do you need to get able to get objects out? Make sure that your container is covariant.
Do you need to be able be able to insert objects? Make sure it's contravariant.