Use-Site Variance and Star Projection

Use-Site Variance

MutableList is invariant, because it both reads and writes. In a single function you can still say "I will only read" (out) or "I will only write" (in). This is called a type projection.

Example

fun copy(from: MutableList<out Number>, to: MutableList<in Number>) {
    for (n in from) to.add(n)       // read from 'from', write to 'to'
}

fun main() {
    val ints = mutableListOf(1, 2, 3)
    val all = mutableListOf<Any>("start")
    copy(ints, all)
    println(all)
}

Output

[start, 1, 2, 3]

Star Projection

Example

List<*> means "a list of some type I do not know". You can read elements as Any? but cannot add to it.

fun printSize(list: List<*>) {
    println("${list.size} items, first = ${list.firstOrNull()}")
}

fun main() {
    printSize(listOf(1, 2, 3))
    printSize(listOf("a", "b"))
    printSize(emptyList<Double>())
}

Output

3 items, first = 1
2 items, first = a
0 items, first = null

Type Erasure

Note: Generic type arguments are erased at runtime on the JVM: if (x is List<String>) is not allowed, only is List<*>. Inline functions with reified type parameters (advanced level) work around this - that is how filterIsInstance<T>() works.