Careeroza
Variance with in and out
The Problem
String is a subtype of Any, but is Box<String> a subtype of Box<Any>? By default no (generics are invariant), because someone could put an Int into a box that should only hold Strings. Variance modifiers tell the compiler when it is safe.
| Modifier | Name | T is used only as... | Subtyping | Example |
|---|---|---|---|---|
out T | covariant (producer) | output (return values) | Source<String> is a Source<Any> | List<out E> |
in T | contravariant (consumer) | input (parameters) | Sink<Any> is a Sink<String> | Comparable<in T> |
| (none) | invariant | both | no relation | MutableList<E> |
out: Producers
Example
Kotlin's read-only List is declared List<out E>, so a List<Int> can be passed where List<Number> is expected.
interface Producer<out T> {
fun produce(): T // T only comes out
}
class OrderIdProducer : Producer<String> {
private var n = 0
override fun produce() = "ORD-" + (++n)
}
fun printNext(p: Producer<Any>) = println(p.produce())
fun sumAll(nums: List<Number>) = nums.sumOf { it.toDouble() }
fun main() {
printNext(OrderIdProducer()) // Producer<String> accepted as Producer<Any>
val ints: List<Int> = listOf(1, 2, 3)
println(sumAll(ints)) // List<Int> accepted as List<Number>
}Output
ORD-1 6.0
in: Consumers
Example
A consumer of Any can safely consume Strings, so with in the subtyping goes the other way.
fun interface Validator<in T> {
fun isValid(value: T): Boolean // T only goes in
}
fun checkName(name: String, v: Validator<String>) = println("'$name' valid? ${v.isValid(name)}")
fun main() {
val notNullish: Validator<Any> = Validator { it.toString().isNotBlank() }
checkName("Asha", notNullish) // Validator<Any> accepted as Validator<String>
checkName(" ", notNullish)
}Output
'Asha' valid? true ' ' valid? false
Tip: Memory trick: out = Output = Producer; in = Input = Consumer.