Variance: in and out
In this page:
The Problem Without Variance
By default, generic types are invariant, meaning Box<Dog> is not automatically treated as a Box<Animal> even if Dog is a subtype of Animal, since that could otherwise let incompatible types be inserted unsafely.
Example: The Problem Without Variance
open class Animal(val name: String)
class Dog(name: String) : Animal(name)
class Box<T>(val item: T)
fun main() {
val dogBox: Box<Dog> = Box(Dog("Rex"))
println(dogBox.item.name)
}
Login to try C/C++/Java/PHP code in the editor
Covariance with out
Marking a type parameter out allows a Producer<Sub> to be used wherever a Producer<Super> is expected, since the type only ever appears as something being produced (returned), which is always safe.
Example: Covariance with out
open class Animal(val name: String)
class Dog(name: String) : Animal(name)
class Producer<out T>(private val item: T) {
fun produce(): T = item
}
fun printAnimalName(producer: Producer<Animal>) {
println(producer.produce().name)
}
fun main() {
val dogProducer: Producer<Dog> = Producer(Dog("Rex"))
printAnimalName(dogProducer)
}
Login to try C/C++/Java/PHP code in the editor
Contravariance with in
Marking a type parameter in allows a Consumer<Super> to be used wherever a Consumer<Sub> is expected, since the type only ever appears as something being accepted (a parameter), which is safe in the opposite direction.
Example: Contravariance with in
open class Animal(val name: String)
class Dog(name: String) : Animal(name)
class Consumer<in T> {
fun consume(item: T) {
println("Consuming an item")
}
}
fun feedDog(consumer: Consumer<Dog>) {
consumer.consume(Dog("Rex"))
}
fun main() {
val animalConsumer: Consumer<Animal> = Consumer()
feedDog(animalConsumer)
}
Login to try C/C++/Java/PHP code in the editor
Kotlin's Built-in List<out T>
The standard library's read-only List<out T> is declared covariant, which is why a List<Dog> can be passed directly wherever a List<Animal> is expected.
Example: Kotlin's Built-in List<out T>
open class Animal(val name: String)
class Dog(name: String) : Animal(name)
fun printNames(animals: List<Animal>) {
for (animal in animals) println(animal.name)
}
fun main() {
val dogs: List<Dog> = listOf(Dog("Rex"), Dog("Fido"))
printNames(dogs)
}
Login to try C/C++/Java/PHP code in the editor
- Using an invariant generic type where a covariant (
out) or contravariant (in) one would allow more flexible, still-safe code. - Marking a type parameter
outand then trying to use it as a function parameter type, which the compiler forbids sinceoutrestricts a type to output positions only. - Confusing
out(covariant, producer) within(contravariant, consumer); they allow opposite kinds of substitutions.
out Tmarks a type parameter as covariant, meaning it can only appear in output (return) positions, and allowsProducer<Sub>to be used whereProducer<Super>is expected.in Tmarks a type parameter as contravariant, meaning it can only appear in input (parameter) positions, and allowsConsumer<Super>to be used whereConsumer<Sub>is expected.- Without
in/out(invariant),Container<Sub>is not automatically substitutable forContainer<Super>, even ifSubis a subtype. - Kotlin's built-in
List<out T>is covariant (read-only), while a hypothetical pure consumer interface would typically be contravariant.
Chapter Quiz — Complete all 6 topics to unlock
0/6 topics done
Complete these topics first: