Generics & Variance Flashcards

KOTLIN › Types & Classes

What do the out and in variance modifiers mean in Kotlin?
out makes the type parameter covariant: it may only appear in out (return/producer) positions, so Box<Derived> is a subtype of Box<Base>. in makes it contravariant: it may only appear in in (parameter/consumer) positions, so Box<Base> is a subtype of Box<Derived>.
What is the difference between declaration-site and use-site variance?
Declaration-site variance is specified once on the type parameter at the class/interface (out T, in T), applying everywhere. Use-site variance (type projection, e.g. Array<out T>) is applied at a specific usage to restrict an otherwise invariant type. Kotlin supports both; Java only has use-site wildcards.
Why is Array<T> invariant in Kotlin while List<T> is covariant?
Array exposes both get (produces T) and set (consumes T), so T appears in both positions and cannot be variant. Read-only List only produces T (out E), so it can be declared covariant and List<String> is a subtype of List<Any>.
What does the star projection Foo<*> mean for an out, in, and invariant parameter?
For Foo<out T : TUpper>, Foo<*> is equivalent to Foo<out TUpper> (safe to read TUpper). For Foo<in T>, it is Foo<in Nothing> (cannot safely write). For an invariant Foo<T : TUpper>, you read as Foo<out TUpper> and write as Foo<in Nothing>.
What is type erasure and which runtime checks does it forbid?
Generic type arguments are erased at compile time, so Foo<Bar> and Foo<Baz?> share one runtime class. You cannot do x is List<Int> (only x is List<*>), and casts like as List<String> are unchecked. Only the raw/star-projected class survives at runtime.
What is a reified type parameter and why must it be inline?
reified makes a type parameter usable at runtime (is T, as T, T::class). It works only in inline functions because inlining copies the function body to each call site, where the concrete type argument is known and substituted directly, sidestepping erasure. A non-inline function has no place to recover the erased type.
Write a reified generic that filters a list to a given type.
inline fun <reified T> List<*>.filterByType(): List<T> = filter { it is T }.map { it as T }. The reified T lets it is T compile; the stdlib equivalent is filterIsInstance<T>().
What are upper bounds and where clauses for type parameters?
An upper bound like <T : Comparable<T>> restricts T to subtypes of the bound (default bound is Any?). Multiple bounds require a where clause: fun <T> f() where T : CharSequence, T : Comparable<T>. T must satisfy all listed constraints simultaneously.
How do noinline and crossinline change lambda behavior in an inline function?
noinline marks a lambda parameter to NOT be inlined, so it can be stored or passed on as a value. crossinline keeps the lambda inlined but forbids non-local returns from it, required when the lambda is invoked from another execution context (e.g. inside a nested object or Runnable).

Back to Generics & Variance