Some say that it is a relationship between types and subtypes, others say that it is a type conversion, while others say that it is used to determine if a method is overwritten or overloaded.
All of the above.
In essence, these terms describe how type conversions affect subtype relationships. That is, if A and B are types, f is a type conversion, and ≤ is a subtype relation (that is, A ≤ B means that A is a subtype of B ), we have
f covariant if A ≤ B means that f(A) ≤ f(B)f contravariant if A ≤ B means that f(B) ≤ f(A)f invariant if none of the above holds.
Let's look at an example. Let f(A) = List<A> where List declared
class List<T> { ... }
Is f covariant, contravariant or invariant? Covariant will mean that List<String> is a subtype of List<Object> , unlike List<Object> is a subtype of List<String> and invariant that none is a subtype of the other, i.e. List<String> and List<Object> are irreversible types. In Java, the latter is true, we say (informally) that generics are invariant.
Another example. Let f(A) = A[] . Is f covariant, contravariant or invariant? That is, String [] is a subtype of Object [], Object [] is a subtype of String [], or is not a subtype of another? (Answer: in Java, arrays are covariant)
It was still pretty abstract. To make it more specific, let's see what operations in Java are defined in terms of a subtype relationship. The simplest example is an assignment. Statement
x = y;
compiles only if typeof(y) ≤ typeof(x) . That is, we just found out that the statements
ArrayList<String> strings = new ArrayList<Object>(); ArrayList<Object> objects = new ArrayList<String>();
will not compile in java but
Object[] objects = new String[1];
will be.
Another example where a subtype relationship matters is a method invocation expression:
result = method(a);
Informally speaking, this operator is evaluated by assigning the value of a first parameter of the method, then executing the method body, and then assigning the return value to the methods for result . As in the usual assignment in the last example, the "right side" must be a subtype of the "left side", that is, this operator can only be valid if typeof(a) ≤ typeof(parameter(method)) and returntype(method) ≤ typeof(result) . That is, if a method is declared:
Number[] method(ArrayList<Number> list) { ... }
none of the following expressions will be compiled:
Integer[] result = method(new ArrayList<Integer>()); Number[] result = method(new ArrayList<Integer>()); Object[] result = method(new ArrayList<Object>());
but
Number[] result = method(new ArrayList<Number>()); Object[] result = method(new ArrayList<Number>());
will be.
Another example where subtypes matter. To consider:
Super sup = new Sub(); Number n = sup.method(1);
Where
class Super { Number method(Number n) { ... } } class Sub extends Super { @Override Number method(Number n); }
Informally, the runtime will rewrite it like this:
class Super { Number method(Number n) { if (this instanceof Sub) { return ((Sub) this).method(n);
For the marked line to compile, the method parameter of the overridden method must be the supertype of the method parameter of the overridden method, and the return type must be the subtype of the overridden method. Formally speaking, f(A) = parametertype(method asdeclaredin(A)) should at least be contravariant, and if f(A) = returntype(method asdeclaredin(A)) should be at least covariant.
Pay attention to the "at least" above. These are the minimum requirements for any reasonable, statically-safe, object-oriented programming language, but the programming language can be more stringent. In the case of Java 1.4, the parameter types and return method types must be identical (except for erasing types) when overriding methods, i.e. type parametertype(method asdeclaredin(A)) = parametertype(method asdeclaredin(B)) when overridden. Starting with Java 1.5, covariant return types are allowed when overriding, that is, the following will compile in Java 1.5, but not in Java 1.4:
class Collection { Iterator iterator() { ... } } class List extends Collection { @Override ListIterator iterator() { ... } }
I hope I covered everything - or rather, scratched the surface. However, I hope this helps to understand the abstract, but important concept of type dispersion.