Variance is a crucial concept in Kotlin's type system, particularly when working with generics. It determines how subtyping relationships between generic types are handled.
In Kotlin, variance comes in three flavors:
Covariance allows you to use a more derived type than originally specified. It's declared using the out
keyword.
interface Producer<out T> {
fun produce(): T
}
With covariance, a Producer<Dog>
can be used where a Producer<Animal>
is expected, assuming Dog is a subtype of Animal.
Contravariance is the opposite of covariance. It allows you to use a more general type than originally specified. It's declared using the in
keyword.
interface Consumer<in T> {
fun consume(item: T)
}
With contravariance, a Consumer<Animal>
can be used where a Consumer<Dog>
is expected.
Invariance is the default behavior in Kotlin. It means there's no subtyping relationship between generic types.
class Box<T>(var value: T)
A Box<Dog>
is not a subtype or supertype of Box<Animal>
.
out
when your class only produces values of type Tin
when your class only consumes values of type TVariance annotations can also be used on individual functions:
fun <T> copyAll(source: MutableList<out T>, destination: MutableList<T>) {
for (item in source) {
destination.add(item)
}
}
This function can copy elements from a list of any subtype of T to a list of T.
To deepen your understanding of Kotlin's type system, explore these related topics:
Mastering variance in Kotlin allows you to write more flexible and reusable generic code. It's an advanced topic, but understanding it can greatly enhance your Kotlin programming skills.