Covariance / Contravariance
In Kotlin generics, adding out (covariance) makes a class a read-only producer, while adding in (contravariance) makes it a write-only consumer. This enables type-safe upcasting and downcasting.
Syntax
// Covariant (out) — can be used as a supertype of T (read-only / Producer)
class Producer<out T>(val value: T) {
fun get(): T = value // OK: can return T
// fun set(v: T) { } // Not allowed: cannot take T as a parameter
}
// Contravariant (in) — can be used as a subtype of T (write-only / Consumer)
class Consumer<in T> {
fun consume(value: T) { println(value) } // OK: can take T as a parameter
// fun get(): T { } // Not allowed: cannot return T
}
Syntax Overview
| Keyword | Name | Description |
|---|---|---|
| out T | Covariant | Assignable to supertypes of T. T can only be used as a return type (Producer). |
| in T | Contravariant | Assignable to subtypes of T. T can only be used as a parameter type (Consumer). |
| T (no variance) | Invariant | Cannot be assigned to supertypes or subtypes. T can be used for both reading and writing. |
| List<out T> | Use-site variance | An example of covariance in the Kotlin standard library. |
Sample Code
// Covariant (out) example — Cage<Dog> can be treated as Cage<Animal>.
open class Animal(val name: String)
class Dog(name: String) : Animal(name)
class Cat(name: String) : Animal(name)
class Cage<out T : Animal>(val animal: T) {
fun getAnimal(): T = animal
}
// Contravariant (in) example — Trainer<Animal> can be treated as Trainer<Dog>.
interface Trainer<in T : Animal> {
fun train(animal: T)
}
fun main() {
// Covariant: Cage<Dog> can be assigned to Cage<Animal>.
val dogCage: Cage<Dog> = Cage(Dog("Pochi"))
val animalCage: Cage<Animal> = dogCage // OK because of out
println(animalCage.getAnimal().name) // Pochi
// Contravariant: Trainer<Animal> can be assigned to Trainer<Dog>.
val animalTrainer: Trainer<Animal> = object : Trainer<Animal> {
override fun train(animal: Animal) {
println("Training ${animal.name}")
}
}
val dogTrainer: Trainer<Dog> = animalTrainer // OK because of in
dogTrainer.train(Dog("Koro")) // Training Koro
// Kotlin's standard List uses out T (covariant)
val dogs: List<Dog> = listOf(Dog("A"), Dog("B"))
val animals: List<Animal> = dogs // OK
println(animals.map { it.name }) // [A, B]
}
Notes
Invariant generics (no variance annotation) are the most restrictive — you cannot assign Box<Dog> to Box<Animal>. A covariant class with out acts as a "producer" that only reads values, while a contravariant class with in acts as a "consumer" that only writes values.
In the Kotlin standard library, List<out E> is the canonical example of covariance. Because it is read-only, it can be assigned to a supertype. In contrast, MutableList<E> is invariant and cannot be assigned this way.
For the basics of generics, see Generics — Basics. For retaining type information at runtime, see Reified Type Parameters.
If you find any errors or copyright issues, please contact us.