Variance: in and out
In this page:
class Producer<out T>(val value: T) // covariant: only produces T
class Consumer<in T> { // contravariant: only consumes T
fun consume(item: T) { }
}
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)
// Define a class `Dog`
class Dog(name: String) : Animal(name)
// Define a class `Box`
class Box<T>(val item: T)
// Entry point: execution of the program starts here
fun main() {
// Declare a read-only variable `dogBox` (a `Box<Dog>`), set to `Box(Dog("Rex"))`
val dogBox: Box<Dog> = Box(Dog("Rex"))
// Print `dogBox.item.name` to the console, with a trailing newline
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)
// Define a class `Dog`
class Dog(name: String) : Animal(name)
// Define a class `Producer`
class Producer<out T>(private val item: T) {
// Define the function `produce` with no parameters and returning a `T`
fun produce(): T = item
}
// Define the function `printAnimalName` taking `producer`
fun printAnimalName(producer: Producer<Animal>) {
// Print `producer.produce().name` to the console, with a trailing newline
println(producer.produce().name)
}
// Entry point: execution of the program starts here
fun main() {
// Declare a read-only variable `dogProducer` (a `Producer<Dog>`), set to `Producer(Dog("Rex"))`
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)
// Define a class `Dog`
class Dog(name: String) : Animal(name)
// Define a class `Consumer`
class Consumer<in T> {
// Define the function `consume` taking `item`
fun consume(item: T) {
// Print "Consuming an item" to the console, with a trailing newline
println("Consuming an item")
}
}
// Define the function `feedDog` taking `consumer`
fun feedDog(consumer: Consumer<Dog>) {
consumer.consume(Dog("Rex"))
}
// Entry point: execution of the program starts here
fun main() {
// Declare a read-only variable `animalConsumer` (a `Consumer<Animal>`), set to `Consumer()`
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)
// Define a class `Dog`
class Dog(name: String) : Animal(name)
// Define the function `printNames` taking `animals`
fun printNames(animals: List<Animal>) {
// Loop over `animals) println(animal.name`, binding each item to `animal`
for (animal in animals) println(animal.name)
}
// Entry point: execution of the program starts here
fun main() {
// Declare a read-only variable `dogs` (a `List<Dog>`), set to `listOf(Dog("Rex"), Dog("Fido"))`
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: