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 of Enumerable<Animal>. The sub-typing is preserved Enumerable 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 of Action<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.