Advanced Generics: Variance
Prerequisites: Generics, Inline functions, reified, and value classes
If String is a subtype of Any, is List<String> a subtype of
List<Any>? The answer depends on variance — whether a generic type
only produces values, only consumes them, or both.
out — producers are covariant
A type that only returns T can be declared out T. Then a
Producer<String> can be used as a Producer<Any>:
fun printAll(items: List<Any>) {
items.forEach { println(it) }
}
fun main() {
val names: List<String> = listOf("Ada", "Linus")
printAll(names) // OK: List is declared List<out E>
}
Ada
Linus
This is safe because a read-only List can only hand out elements —
nobody can put a number into your list of names through it.
MutableList<String> is not a MutableList<Any>: if it were, code
could add an Int to a list that's supposed to contain only strings.
in — consumers are contravariant
A type that only accepts T can be declared in T. A
Comparator<CharSequence> can compare any String, so it can be used
where a Comparator<String> is needed:
fun main() {
val byLength = Comparator<CharSequence> { a, b -> a.length - b.length }
val words = mutableListOf("kiwi", "fig", "banana")
words.sortWith(byLength) // sortWith takes Comparator<in String>
println(words)
}
[fig, kiwi, banana]
A handy memory aid: Producer out, Consumer in.
Declaring variance
interface Source<out T> {
fun next(): T // T only comes out
}
interface Sink<in T> {
fun accept(item: T) // T only goes in
}
The compiler checks you keep the promise: an out T type can't have a
method that takes T as a parameter.
Star projection
When you don't know or care about the type argument, use *:
fun describe(items: List<*>) = "${items.size} items, first is ${items.firstOrNull()}"
fun main() {
println(describe(listOf(1, 2, 3)))
println(describe(listOf("a")))
}
3 items, first is 1
1 items, first is a
You can read from a List<*> (as Any?), but can't add to a
MutableList<*>, because the element type is unknown.
Do you need this?
Mostly when designing libraries or generic utilities. In application
code, knowing why List<String> fits where List<Any> is expected —
and MutableList<String> doesn't — is usually enough.