Question

Java lesson on generics are leading me to variance concept. This causes me some headaches as I cannot find a very simple demonstration of what it is.

I have read several similar questions on stackoverflow, but I found them too difficult to understand for a Java learner. Actually the problem is that the explanation of generics requires variance to be understood, and the concept of variance is demonstrated relying heavily on generics understanding.

I had some hope reading this, but in the end I shared C. R.'s feeling:

The title reminds me of the days learning general relativity. – C.R. Dec 22 '13 at 7:34

Four theory questions are very confusing to me, and I cannot find good and simple explanations. Here they are, with my current partial understanding (I fear experts will have a great fun reading this).

Your help to correct and clarify is welcome (remember this is for beginners, not for experts).

Is there something wrong with this understanding?

  1. What is invariance / covariance / contravariance related to in the context of programing? My best guess is that:
    • This is something encountered in object-oriented programing.
    • This has to do when looking at method arguments and result type in the class and an ancestor.
    • This is used in the context of method overriding and overloading.
    • This is used to establish a connection between the type of a method argument, or the method return type, and the inheritance of the classes themselves, e.g. if class D is a descendant of class A, what can we say about the types of arguments and the method method return type?
  2. How variance relates to Java methods? My best guess is that, given two classes A and D, with A being an ancestor of D, and a overhiden/overloaded method f(arg):
    • If the relation between the argument type in the two methods IS THE SAME than the relation between the two classes, the argument type in the method is said COVARIANT with the class type, said otherwise: the inheritance between arg types in A and D is covariant with the inheritance of classes A and D.
    • If the relation between the arguments REVERSES the relation between classes, the arg type is said CONTRAVARIANT to the class type, said otherwise: the inheritance between arg types in A and D is contravariant with the inheritance of classes A and D..
  3. Why is variance understanding so important for Java programmers? My guess is that:
    • Java language creators have implemented rules for variance in the language, and this has implications on what a programmer can do.
    • A rule states that the return type of an overriding/overloading method must be contravariant to the inheritance.
    • Another rule states that the type of an argument of an overriding/overloading must be is covariant to the inheritance.
    • The Java compiler checks the variance rules are valid, and provides errors or warnings accordingly. Deciphering the messages is easier with variance knowledge.
  4. What is the difference between overrhiding and overloading? Best guess:
    • A method overrides another method when argument and return types are both invariant. All other cases are understood by the compiler as overloading.
Was it helpful?

Solution

This is not specific to OO, but has to do with the properties of certain types.

For example, with the function type

 A -> B                 // functional notation
 public B meth(A arg)   // how this looks in Java 

we have the following:

Let C be a subtype of A, and D be a subtype of B. Then the following is valid:

 B b       = meth(new C());  // B >= B, C < A
 Object o  = meth(new C());  // Object > B, C < A

but the follwoing are invalid:

 D d       = meth(new A());        // because D < B
 B b       = meth(new Object());   // because Object > A

hence, to check whether a call of meth is valid, we must check

  • The expected return type is a supertype of the declared return type.
  • The actual argument type is a subtype of the declared argument type.

This is all well known and intuitive. By convention we say that the return type of a function is covariant, and the argument type of a method is contravariant.

With parameterized types, like List, we have it that the argument type is invariant in languages like Java, where we have mutability. We can't say that a list of C's is a list of A's, because, if it were so, we could store an A in a list of Cs, much to the surprise of the caller, who assumes only Cs in the list. However, in languages where values are immutable, like Haskell, this is not a problem. Because the data we pass to functions cannot be mutated, a list of C actually is a list of A if C is a subtype of A. (Note that Haskell has no real subtyping, but has instead the related notion of "more/less polymorphic" types.)

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top