Author profile picture

Variance Annotation

A variance annotation is a modifier applied to a type parameter or type argument of a generic, in order to declare its variance.

Kotlin defines two variance annotations in its grammar: out and in.

out

The out modifier is used to declare a type parameter as being covariant. For example:

class Box<out T>(private val item: T) {
    fun getItem(): T = item
}

With the out variance annotation declared on the type parameter T in this class, subtyping rules can now be applied to Boxes of different types. For example:

val integer: Box<Int> = Box(1)
val number: Box<Number> = integer

Notice how number, although declared as a Box<Number>, refers to an object of type Box<Int>. In other words, Box<Int> is now a subtype of Box<Number>.

Also note that in our class, T is only ever used in the “out” position - it’s only ever returned from a function. By declaring it to be out, the compiler will no longer allow the class to accept a function argument of type T (other than within the constructor).

in

The in modifier is used to declare a type parameter as being contravariant. For example:

class Box<in T>(private var item: T) {
    fun setItem(item: T) {
        this.item = item
    }
}

The in variance annotation on type parameter T causes subtyping rules to apply to Box in the opposite direction to the subtyping rules of the classes that it wraps. For example:

val number: Box<Number> = Box(1)

val integer: Box<Int> = number
val double: Box<Double> = number

Because of the in modifier, Box<Number> is now a subtype of both Box<Int> and Box<Double>.

By adding in to the type parameter, the compiler will only ever allow us to use the type parameter in the “in” position - as a function argument. It can never be returned by a function.

No variance annotation

If no variance annotation is declared on a type parameter, then by default, the it’ll be invariant. For example:

class Box<T>(private var item: T) {
    fun getItem(): T = item
    fun setItem(item: T) {
        this.item = item
    }
}

In this case, no subtyping rules will be applied to Box, so neither Box<Number> nor Box<Int> is a subtype of the other.

Share this article:

  • Share on Twitter
  • Share on Facebook
  • Share on Reddit