Generics, variance and delegation
Type parameters and bounds, declaration-site and use-site variance, star projections, reified inline generics, and delegation with by.
Variance explained by use
// out: a producer. Read values out of it, never put them in.
interface Source<out T> {
fun next(): T
}
// in: a consumer. Put values in, never read them out.
interface Sink<in T> {
fun accept(value: T)
}
class NumberSource : Source<Number> {
override fun next(): Number = 42
}
// Source<Int> is a subtype of Source<Number> because T is covariant
val source: Source<Number> = NumberSource()
val widened: Source<Any> = source
// List is declared as List<out E>, so this is legal
fun printAll(items: List<Any>) = items.forEach(::println)
printAll(listOf(1, "two", 3.0))
// MutableList is invariant: writing and reading make both directions unsafe
// fun mutate(items: MutableList<Any>) { } // MutableList<Int> will NOT fitouton a type parameter means it only appears in output positions;inmeans only in input positions.- Kotlin declares variance at the declaration site, unlike Java's wildcards at each use site.
- Use-site variance is still available when a type is invariant but you know how you will use it:
Array<out Any>. - A
List<T>in a function parameter is usually the wrong shape if you intend to add to it; ask forMutableList<T>or return a new list.
Bounds, projections and reified
fun <T : Comparable<T>> maxOfList(items: List<T>): T {
require(items.isNotEmpty()) { "list must not be empty" }
return items.reduce { a, b -> if (a > b) a else b }
}
// multiple bounds need a where clause
fun <T> merge(a: T, b: T): T where T : CharSequence, T : Appendable = a
// star projection: the element type is unknown, so you can only read Any?
fun size(items: List<*>) = items.size
// reified keeps the type at runtime, which requires inline
inline fun <reified T> List<*>.filterIsInstanceOf(): List<T> =
filterIsInstance<T>()
// JSON-like tree that benefits from a sealed hierarchy rather than generics
sealed interface Node<out T> {
data class Leaf<T>(val value: T) : Node<T>
data class Branch<T>(val left: Node<T>, val right: Node<T>) : Node<T>
}| Construct | Meaning | When to use |
|---|---|---|
<out T> | Covariant producer | Read-only collections, results, factories |
<in T> | Contravariant consumer | Comparators, handlers, callbacks |
<*> | Unknown type | You only need size or membership |
reified | Type available at runtime | Parsing, filtering by class, building maps |
Delegation with by
interface Logger {
fun log(message: String)
}
class ConsoleLogger : Logger {
override fun log(message: String) = println(message)
}
// implementation delegation: forward Logger to the delegate
class Service(logger: Logger) : Logger by logger {
fun handle() {
log("handling") // no overriding boilerplate at all
}
}
// property delegation
import kotlin.properties.Delegates
class Settings {
var theme: String by Delegates.observable("light") { _, old, new ->
println("theme changed from $old to $new")
}
var token: String? by Delegates.vetoable(null) { _, _, new ->
new.isNullOrBlank().not() // reject blank values
}
}
// lazy is the most common delegation and is thread-safe by default
class Repository {
val client: HttpClient by lazy { HttpClient() }
}💡
A custom delegate is just an object with
getValue and setValue operators. That is how most Android frameworks implement by viewModels() — storing the value in a map keyed by property so it survives configuration changes.FAQ
Why can I pass a <code>List<String></code> where <code>List<Any></code> is expected?
Because
List is declared with out E. It is read-only, so a list of strings is safely usable as a list of anything — you can only ever get values out of it.When should I use <code>reified</code>?
When you need the actual class at runtime: filtering by type, deserialising, or building a map keyed by class. It forces
inline, which increases bytecode size at each call site, so keep such functions small.Related
Functional Kotlin: lambdas, inline functions and DSLs Java interop, JVM tooling and build performance
Last refreshed 2026-09-18.