Today, I read some articles about Covariance, Contravariance (and Invariance) in Java. I read the English and German Wikipedia article, and some other blog posts and articles from IBM.
But I’m still a little bit confused on what these exactly are about? Some say it’s about relationship between types and subtypes, some say it’s about type conversion and some say it’s used to decide whether a method is overridden or overloaded.
So I’m looking for an easy explanation in plain English, that shows a beginner what Covariance and Contravariance (and Invariance) is. Plus point for an easy example.
All of the above.
At heart, these terms describe how the subtype relation is affected by type transformations. That is, if
AandBare types,fis a type transformation, and ≤ the subtype relation (i.e.A ≤ Bmeans thatAis a subtype ofB), we havefis covariant ifA ≤ Bimplies thatf(A) ≤ f(B)fis contravariant ifA ≤ Bimplies thatf(B) ≤ f(A)fis invariant if neither of the above holdsLet’s consider an example. Let
f(A) = List<A>whereListis declared byIs
fcovariant, contravariant, or invariant? Covariant would mean that aList<String>is a subtype ofList<Object>, contravariant that aList<Object>is a subtype ofList<String>and invariant that neither is a subtype of the other, i.e.List<String>andList<Object>are inconvertible types. In Java, the latter is true, we say (somewhat informally) that generics are invariant.Another example. Let
f(A) = A[]. Isfcovariant, contravariant, or invariant? That is, is String[] a subtype of Object[], Object[] a subtype of String[], or is neither a subtype of the other? (Answer: In Java, arrays are covariant)This was still rather abstract. To make it more concrete, let’s look at which operations in Java are defined in terms of the subtype relation. The simplest example is assignment. The statement
will compile only if
typeof(y) ≤ typeof(x). That is, we have just learned that the statementswill not compile in Java, but
will.
Another example where the subtype relation matters is a method invocation expression:
Informally speaking, this statement is evaluated by assigning the value of
ato the method’s first parameter, then executing the body of the method, and then assigning the methods return value toresult. Like the plain assignment in the last example, the “right hand side” must be a subtype of the “left hand side”, i.e. this statement can only be valid iftypeof(a) ≤ typeof(parameter(method))andreturntype(method) ≤ typeof(result). That is, if method is declared by:none of the following expressions will compile:
but
will.
Another example where subtyping matters is overriding. Consider:
where
Informally, the runtime will rewrite this to:
For the marked line to compile, the method parameter of the overriding method must be a supertype of the method parameter of the overridden method, and the return type a subtype of the overridden method’s one. Formally speaking,
f(A) = parametertype(method asdeclaredin(A))must at least be contravariant, and iff(A) = returntype(method asdeclaredin(A))must at least be covariant.Note the “at least” above. Those are minimum requirements any reasonable statically type safe object oriented programming language will enforce, but a programming language may elect to be more strict. In the case of Java 1.4, parameter types and method return types must be identical (except for type erasure) when overriding methods, i.e.
parametertype(method asdeclaredin(A)) = parametertype(method asdeclaredin(B))when overriding. Since Java 1.5, covariant return types are permitted when overriding, i.e. the following will compile in Java 1.5, but not in Java 1.4:I hope I covered everything – or rather, scratched the surface. Still I hope it will help to understand the abstract, but important concept of type variance.