Is covariance, invariance and contravariance explained in plain English?

Today I read several articles about covariance, contravariance (and invariance) in Java. I read the English and German Wikipedia article, as well as some other blog posts and articles from IBM.

But am I still a little confused about what it is? Some talk about the relationship between types and subtypes, others talk about type conversion, and some say that he used to decide whether a method was overridden or overloaded.

Therefore, I am looking for a simple explanation in plain English that shows the beginner what Covariance and Contravariance (and Invariance) are. Plus for a simple example.

+107
java covariance contravariance
Dec 12 2018-11-21T00:
source share
2 answers

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); // * } else { ... } } } 

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.

+259
Dec 12 '11 at
source share

Take a system like java and then classes:

Any object of some type T can be replaced by an object of subtype T.

TYPE OPTIONS - CLASS METHODS HAVE THE FOLLOWING CONSEQUENCES

 class A { public S f(U u) { ... } } class B extends A { @Override public T f(V v) { ... } } B b = new B(); t = bf(v); A a = ...; // Might have type B s = af(u); // and then do V v = u; 

You can see that:

  • T must be a subtype of S ( covariant since B is a subtype of A ).
  • V must be a supertype of U ( contravariant as the opposite direction of inheritance).

Now co-contradicts the fact that B is a subtype of A. The following stronger types can be introduced with more specific knowledge. In the subtype.

Covariance (available in Java) is useful to say that a more specific result is returned in a subtype; especially when A = T and B = S. Contravariance says you're ready to handle a more general argument.

+11
Dec 12 '11 at 23:12
source share



All Articles