38. Generics & Variance
Generics let functions and classes work over any type while staying type-safe. An upper bound like <T : Comparable<T>> constrains what T may be.
// generic function
fun <T> firstOf(items: List<T>): T = items[0]
// generic class
class Box<T>(val value: T)
// upper-bound constraint
fun <T : Comparable<T>> maxOf(a: T, b: T): T = if (a > b) a else b
Variance controls how generic types relate as their type arguments change. A producer that only returns T is declared out (covariant); a consumer that only accepts T is declared in (contravariant).
// 'out' = producer (covariant): can only return T
class Producer<out T>(private val value: T) {
fun get(): T = value
}
// 'in' = consumer (contravariant): can only accept T
class Consumer<in T> {
fun consume(item: T) = println("consumed $item")
}
fun main() {
println(firstOf(listOf("a", "b")))
println(Box(42).value)
println(maxOf(3, 7))
val producer: Producer<Any> = Producer<String>("hi") // covariance
println(producer.get())
val consumer: Consumer<String> = Consumer<Any>() // contravariance
consumer.consume("ok")
}
The mnemonic is producer-out, consumer-in: a Producer<String> is safely a Producer<Any>, and a Consumer<Any> is safely a Consumer<String>.
Running it:
$ kotlin run
a
42
7
hi
consumed ok
| ← Prev | Index | Next → |