You never touched Groovy, nor did you jump on the Scala train. Clojure never attracted you; and you heard about Ceylon long after the language had already died. You are one of those old-fashioned Java folks! But now, after all those years, you want to join the cool Kotlin kids. So, where to start? Let’s discover the language together by decompiling it to Java code. Today: Covariance, contravariance and invariance!

Today: Covariance, contravariance and invariance

Hurrah, you’ve mastered the basics. More and more code flows out of your hands. But then you come across something you haven’t seen before. Sometimes there seem to be in and out keywords before a generic type. For example, take Kotlin’s functional interface:

public interface Function<out R>

Let’s see what these keywords mean by decompiling it to Java code:

public interface Function {
}

Ugh, the <R> is gone! So that’s what they mean by type erasure… The JVM simply doe not have any information about generic type parameters[1]. So this time, decompiling bytecode back to Java code for understanding won’t help us much!

Why do generic types even exist in JVM languages like Kotlin, if it doesn’t exist at JVM level at all? Well, having generics are a way for the programmer to express code in a type-safe manner. If you would look to above example again, using a type R is way clearer than just dealing with whatever object.

When writing Java code, you’ve probably seen the <? extends T> and <? super T> generic wildcard types. The <? extends T> means something like type T and its children, where <? super T> means something like type T and its parents. The former is called covariance, the latter contravariance. To make it complete, having just <T> is called invariance. In Java, you declare the variance when you are using it, such as when you create a variable:

class InvariantBox<T> {
    private final T value;

    public InvariantBox(T value) {
        this.value = value;
    }

    public T getValue() {
        return value;
    }
}

InvariantBox<? extends Number> numberBox = new InvariantBox<>(42); // Defined as invariant <T>, still the numberBox is covariant by its wildcard
Number x = numberBox.getValue();  // works
Integer y = numberBox.getValue(); // compile error: capture of ? extends Number

That’s why this way of working with variance is called 'use-site' variance. As it turned out, Kotlin took a different route concerning type variance than Java did. It uses "mixed-site" variance, which gives you the option to define variance when you declare and when you use it[2]. Kotlin still let you use 'use-site' variance. Above example could be written in Kotlin like this:

class InvariantBox<T>(val value: T)

val numberBox: InvariantBox<out Number> = InvariantBox(42)
val x: Number = numberBox.value // This works
val y: Int = numberBox.getValue() // compile error: Unresolved reference

By comparing the code with the Java one, we can conclude out means its covariance. Thus, the correct guess would be to imply that in means its contravariance. In addition, it is also possible to use 'declaration-site' variance when defining classes and interfaces:

class CovariantBox<out T>(val value: T)
class ContravariantBox<in T>

Okay, but what use is this to you? Well if you would use invariance, you cannot loosen the type even if it 'should be' possible:

val boxOfString: InvariantBox<String> = InvariantBox("Hello")
val boxOfAny: InvariantBox<Any> = boxOfString // compile error: Type mismatch, required: InvariantBox<Any>, found: InvariantBox<String>

If you were to go for covariance, you could very well do this:

val boxOfString: CovariantBox<String> = CovariantBox("Hello")
val boxOfAny: CovariantBox<Any> = boxOfString // works!

Nice! The inverse, contravariance, does work too:

val boxOfAny = ContravariantBox<Any>()
val boxOfString: ContravariantBox<String> = boxOfAny // works!

But be warned, 'declaration-site' variance comes with an important caveat. There is a reason why Kotlin uses the in and out keywords for co- and contravariance, instead of going for something like - and + as Scala and OCaml did[3]. Using such variance types also limits their behavior in the defined class or interface. If you use contravariance, you 'consume' a generic type and therefore cannot return said type. This sounds a bit difficult, so take a look at the following example:

class ContravariantBox<in T> {
  fun print(value: T) = println(value)
  fun run(): T = TODO() // compile error: Type parameter T is declared as 'in' but occurs in 'out' position in type T
}

Ah, it’s not so hard after all; the ContravariantBox class can use the type as an input parameter for the print function, but cannot expose it as return type for the run function. Or speaking more abstractly, contravariance types can only be used as input parameters, it cannot be used as return types. And the same goes for covariance, albeit the other way around:

class CovariantBox<out T>(val value: T) {
  fun print(value: T) = println(value) // compile error: Type parameter T is declared as 'out' but occurs in 'in' position in type T
  fun run(): T = TODO()
}

The CovariantBox class 'produces' the type as return type for the run function, but cannot use it as input parameter. In other words, covariant types cannot be used as input parameters, but can be used as output!

Knowing this, it is worth noting that you can combine the two. Again, looking at Kotlin’s functional interfaces will give you a clear impression:

public interface Function2<in P1, in P2, out R> : Function<R> {
  public operator fun invoke(p1: P1, p2: P2): R
}

Well, that’s enough for one day. Stay tuned for more!


1. The reason the JVM does not support type parameters is purely for backward compatibility between java 1.4 and 1.5. It also implies the implementation of generics may vary by JVM language, because the compiler of such language must erase this information when creating the bytecode.
2. The language designers worked together with Ross Tate, an assistant professor at Cornell University. His paper about this topic is worth checking out.
3. Well, to be honest, the Kotlin devs took those keyword names from C#.
shadow-left