Variance and reified generics explain library signatures and let you write type-safe, reusable APIs.
Generic classes and functions work much like Java's: class Box<T>(val value: T), fun <T> List<T>.second(): T. Upper bounds restrict type arguments: fun <T : Comparable<T>> maxOf(a: T, b: T); several bounds use where T : A, T : B.
Variance decides when Box<Cat> can be used as Box<Animal>. Kotlin uses declaration-site variance:
out T(covariant): the class only producesT. Kotlin'sList<out E>is read-only, so aList<String>is aList<Any>.in T(contravariant): the class only consumesT, likeComparable<in T>. AComparator<Any>can compare strings.- No modifier: invariant.
MutableList<String>is not aMutableList<Any>, otherwise you could add anIntto it.
The compiler enforces that out types appear only in output positions and in types only in inputs. Use-site variance (fun copy(from: Array<out Any>, to: Array<Any>)) handles classes that aren't variant themselves. A star projection List<*> means "a list of some unknown type": safe to read as Any?, not to write.
On the JVM, type arguments are erased at runtime, so x is List<String> isn't possible. Reified type parameters on inline functions keep the type: inline fun <reified T> Any.isA() = this is T. That's how filterIsInstance<T>() works.
Example
open class Animal(val name: String)
class Cat(name: String) : Animal(name)
class Producer<out T>(private val item: T) { fun get(): T = item }
class Consumer<in T> { fun accept(x: T) = println("accepted $x") }
fun <T : Comparable<T>> maxOf3(a: T, b: T, c: T): T = maxOf(a, maxOf(b, c))
inline fun <reified T> List<Any>.only(): List<T> = filterIsInstance<T>()
fun names(xs: List<*>) = xs.map { it.toString() } // star projection
fun main() {
val cats: Producer<Cat> = Producer(Cat("Tom"))
val animals: Producer<Animal> = cats // OK: covariant
println(animals.get().name) // Tom
val anyConsumer = Consumer<Any>()
val strConsumer: Consumer<String> = anyConsumer // OK: contravariant
strConsumer.accept("hi") // accepted hi
println(maxOf3(3, 9, 4)) // 9
println(listOf(1, "a", 2.5, "b").only<String>()) // [a, b]
println(names(listOf(1, null))) // [1, null]
// val bad: MutableList<Any> = mutableListOf<String>() // error: invariant
}Remember
- out = producer (covariant), in = consumer (contravariant)
- List<out E> is covariant; MutableList<E> is invariant
- Generics are erased on the JVM; inline + reified keeps the type
- List<*> reads as Any? but can't be written to