SkillByAIOpen interactive version →

Lesson 20 / 25

Generics and Variance

Type parameters, out, in, constraints and reified.

Producers out, consumers in

Generic classes and functions take type parameters: class Box<T>(val value: T), fun <T> firstOf(list: List<T>): T. Declaration-site variance states how a type parameter is used: out T means the type only produces T (so List<String> is a List<out Any>, and Kotlin List<out E> is covariant), and in T means it only consumes T (a Comparator<Any> can be used as a Comparator<String>). Use-site projections like Array<out Number> do the same at a single usage, and * is the star projection. Upper bounds constrain types: <T : Comparable<T>>. Generics are erased at runtime, but an inline function with a reified type parameter can use T::class and is T.

Variance, bounds and reified

Generic code that stays type-safe.

interface Source<out T> { fun next(): T }           // producer: covariant
interface Sink<in T> { fun put(item: T) }           // consumer: contravariant

fun <T : Comparable<T>> maxOf3(a: T, b: T, c: T): T = maxOf(a, maxOf(b, c))

fun copyAll(from: Source<Number>, to: Sink<Number>, n: Int) {
    repeat(n) { to.put(from.next()) }
}

inline fun <reified T> List<Any>.only(): List<T> = filter { it is T }.map { it as T }

fun main() {
    val ints: Source<Int> = object : Source<Int> { override fun next() = 1 }
    val numbers: Source<Number> = ints                  // allowed because of out
    val mixed: List<Any> = listOf(1, "two", 3.0)
    val strings: List<String> = mixed.only<String>()    // reified type check
}

Vending machines and recycling bins

A vending machine of oranges can serve someone who wants any fruit (out: producer). A bin that accepts any fruit can be used by someone throwing away oranges (in: consumer).

Quick check: Why is a `reified` type parameter useful?

  • It makes a type parameter nullable
  • It lets an inline function check or access the actual type at runtime
  • It disables type erasure for all classes
  • It makes the function run faster on all platforms
Answer

It lets an inline function check or access the actual type at runtime — Only inline functions can have reified type parameters.