kotlinadvanced
Kotlin Generics Variance In Out Star
Understand Kotlin generics: declaration-site variance with in/out, type projections, and star projection.
kotlinPress ⌘/Ctrl + Shift + C to copy
// out = covariant (producer) — can only return T
interface Source<out T> {
fun next(): T
}
// in = contravariant (consumer) — can only accept T
interface Consumer<in T> {
fun consume(item: T)
}
// Invariant (default) — can both accept and return T
interface MutableBox<T> {
fun get(): T
fun set(value: T)
}
// Real-world example: Result container
sealed class Result<out T> {
data class Success<T>(val value: T) : Result<T>()
data class Error(val message: String) : Result<Nothing>() // Nothing is subtype of everything
}
// Comparator is contravariant
class AgeComparator : Comparator<Person> {
override fun compare(a: Person, b: Person) = a.age.compareTo(b.age)
}
// Generic functions with upper bounds
fun <T : Comparable<T>> maxOf(a: T, b: T): T = if (a >= b) a else b
// Multiple upper bounds
fun <T> ensureSerializable(value: T): T where T : Comparable<T>, T : java.io.Serializable {
return value
}
// Star projection: unknown type
fun printAll(list: List<*>) {
list.forEach { println(it) }
}
// Reified to avoid type erasure
inline fun <reified T> List<*>.filterByType(): List<T> {
return filterIsInstance<T>()
}
open class Person(val name: String, val age: Int)
class Student(name: String, age: Int, val grade: String) : Person(name, age)
fun main() {
// Covariance: Source<Student> is subtype of Source<Person>
val studentSource: Source<Student> = object : Source<Student> {
override fun next() = Student("Alice", 20, "A")
}
val personSource: Source<Person> = studentSource // OK! out variance
println("Person: ${personSource.next().name}")
// Contravariance: Consumer<Person> is subtype of Consumer<Student>
val personConsumer: Consumer<Person> = object : Consumer<Person> {
override fun consume(item: Person) = println("Consuming: ${item.name}")
}
val studentConsumer: Consumer<Student> = personConsumer // OK! in variance
studentConsumer.consume(Student("Bob", 21, "B"))
// Result with Nothing
val success: Result<String> = Result.Success("Hello")
val error: Result<String> = Result.Error("Oops") // Nothing fits any T
when (success) {
is Result.Success -> println("Got: ${success.value}")
is Result.Error -> println("Error: ${success.message}")
}
// Star projection
val mixed: List<*> = listOf(1, "hello", 3.14)
printAll(mixed)
// Filter by type
val strings = mixed.filterByType<String>()
println("Strings: $strings")
// Upper bounds
println("Max: ${maxOf(10, 20)}")
println("Max: ${maxOf("apple", "banana")}")
}Use Cases
- Type-safe container hierarchies
- Producer/consumer API design
- Generic utility functions with constraints
Tags
Related Snippets
Similar patterns you can reuse in the same workflow.
kotlinadvanced
Generics — Variance, Bounds, and Type Projections
Master Kotlin generics: in/out variance, upper bounds, type projections, and generic constraints.
Best for: Type-safe generic APIs and containers
#kotlin#generics
kotlinintermediate
Sealed Classes and Exhaustive when
Model restricted hierarchies with sealed classes: exhaustive when, data objects, and state machines.
Best for: Type-safe API response handling
#kotlin#sealed-class
kotlinadvanced
Inline Functions and Reified Generics
Use inline functions for zero-overhead abstractions: reified type parameters, crossinline, and noinline.
Best for: Type-safe parsing and casting utilities
#kotlin#inline
kotlinintermediate
Value Classes — Zero-Cost Wrappers
Create type-safe wrappers with value classes: no runtime overhead, domain identifiers, and units.
Best for: Type-safe domain identifiers
#kotlin#value-class