Varianz an der Deklarationsstelle: in und out
Verwenden Sie kovariante (out) und kontravariante (in) Typparameter korrekt.
Varianz an der Deklarationsstelle: in und out ist eine kostenlose Kotlin Academy-Lektion auf CoddyKit. Dies ist Lektion 2 von 4. Du kannst die komplette Lektion unten kostenlos lesen – dann übst du sie direkt im Browser mit einem integrierten Code-Editor und einem KI-Tutor rund um die Uhr. Sie ist Teil des Kotlin Academy-Lernpfads, und dein Fortschritt wird über Web und CoddyKit-App synchronisiert. Der Kotlin Academy-Kurs umfasst insgesamt 4 Lektionen.
Das Varianzproblem
In Kotlin ist List<String> ein Subtyp von List<Any>, weil List mit out T deklariert ist. Ohne Varianzannotationen wäre dies nicht der Fall.
val strings: List<String> = listOf("a", "b")
val anys: List<Any> = strings // OK because List<out T>
// MutableList<String> is NOT a subtype of MutableList<Any>:
// val m: MutableList<Any> = mutableListOf("x") // compile error
fun main() { println(anys) }Kovarianz mit out
out T bedeutet, dass die Klasse nur T-Werte erzeugen (zurückgeben), aber niemals konsumieren kann. Dadurch ist Producer<Dog> ein Subtyp von Producer<Animal>.
interface Producer<out T> {
fun produce(): T
}
class DogProducer : Producer<String> {
override fun produce() = "Woof!"
}
fun sound(p: Producer<Any>) = println(p.produce())
fun main() {
val dog = DogProducer()
sound(dog) // OK: Producer<String> is a subtype of Producer<Any>
}Kontravarianz mit in
in T bedeutet, dass die Klasse nur T-Werte konsumieren (als Parameter akzeptieren), aber niemals erzeugen kann. Dadurch ist Consumer<Animal> ein Subtyp von Consumer<Dog>.
interface Consumer<in T> {
fun consume(item: T)
}
class Printer : Consumer<Any> {
override fun consume(item: Any) = println(item)
}
fun feedDog(consumer: Consumer<String>) = consumer.consume("Dog")
fun main() {
val printer = Printer()
feedDog(printer) // OK: Consumer<Any> is a subtype of Consumer<String>
}out-Einschränkung: keine In-Position
Bei out T darf T nicht an einer Parameterposition (in-Position) vorkommen. Der Compiler setzt dies durch.
interface ReadOnly<out T> {
fun get(): T // OK: out position
// fun set(t: T) {} // Error: T in in-position violates out variance
}
class Box<out T>(private val value: T) : ReadOnly<T> {
override fun get() = value
}
fun main() {
val box: ReadOnly<Any> = Box<String>("hello")
println(box.get())
}in-Einschränkung: keine Out-Position
Bei in T darf T nicht an einer Rückgabeposition (out-Position) vorkommen.
interface WriteOnly<in T> {
fun set(t: T) // OK: in position
// fun get(): T {} // Error: T in out-position violates in variance
}
class Sink<in T> : WriteOnly<T> {
override fun set(t: T) = println("Received: $t")
}
fun main() {
val sink: WriteOnly<String> = Sink<Any>()
sink.set("hello")
}Beispiele aus der Kotlin-Standardbibliothek
Comparable<in T> ist kontravariant: Ein Comparable<Number> kann jeden Number-Subtyp vergleichen. List<out T> ist kovariant: Eine Liste von Katzen ist eine Liste von Tieren.
fun sortNumbers(list: List<Number>) = list.sortedWith(compareBy { it.toDouble() })
fun main() {
val ints: List<Int> = listOf(3, 1, 2)
val sorted = sortNumbers(ints) // OK: List<Int> is List<Number>
println(sorted)
}Invarianz: MutableList
MutableList<T> ist invariant: Sie kann T sowohl erzeugen als auch konsumieren, daher besteht zwischen verschiedenen T-Typen keine Subtypbeziehung.
fun addNumber(list: MutableList<Number>) { list.add(1.5) }
fun main() {
val ints = mutableListOf<Int>(1, 2)
// addNumber(ints) // compile error: MutableList<Int> is not MutableList<Number>
val nums = mutableListOf<Number>(1, 2)
addNumber(nums) // OK
println(nums)
}Use-Site-Varianz als Alternative
Wenn Sie die Klasse nicht ändern können, verwenden Sie Use-Site-Varianz: out an der Aufrufstelle, um einen Typ kovariant zu projizieren.
fun copy(from: MutableList<out Any>, to: MutableList<Any>) {
for (item in from) to.add(item)
}
fun main() {
val src = mutableListOf("a", "b", "c")
val dest = mutableListOf<Any>()
copy(src, dest)
println(dest)
}Praktisch: Repository-Muster
Verwenden Sie Kovarianz für schreibgeschützte Repositories und Kontravarianz für reine Schreibsenken, um einen klaren Datenfluss abzubilden.
interface Repository<out T> {
fun findAll(): List<T>
fun findById(id: Int): T?
}
interface Writer<in T> {
fun save(entity: T)
}
interface ReadWriteRepo<T> : Repository<T>, Writer<T>
// ReadWriteRepo<User> is neither sub nor super of ReadWriteRepo<Admin>Leitfaden zur Varianzentscheidung
Wählen Sie die Varianz anhand der Verwendung von T: out = Produzent (gibt T nur zurück), in = Konsument (akzeptiert T nur), invariant = beides (MutableList, Channel).
// Quick mental model:
// out T: source/producer — List, Flow, Sequence
// in T: sink/consumer — Comparable, Continuation
// invariant: read+write — MutableList, Channel, MutableStateFlow
fun main() {
val nums: List<Number> = listOf(1, 2, 3) // List is out
println(nums)
}Invariante Klasse mit out-Funktion
Auch in einer invarianten Klasse können Sie out an der Aufrufstelle für bestimmte Funktionsparameter verwenden.
class Stack<T>(private val items: MutableList<T> = mutableListOf()) {
fun push(item: T) = items.add(item)
fun pop(): T? = items.removeLastOrNull()
}
fun printAll(stack: Stack<out Any>) {
// can only read, not write
println(stack.pop())
}
fun main() {
val s = Stack<String>()
s.push("hello")
printAll(s)
}Schnelltest
Was bedeutet out T bei einem Typparameter?
Zusammenfassung
out T (Kovarianz) ermöglicht Subtypbeziehungen beim Erzeugen von Werten. in T (Kontravarianz) ermöglicht Subtypbeziehungen beim Konsumieren von Werten. Invariante Typen (beim Lesen und Schreiben) haben für verschiedene T-Typen keine Subtypbeziehung.
Häufig gestellte Fragen
Ist die Lektion „Varianz an der Deklarationsstelle: in und out“ kostenlos?
Ja — der vollständige Text von „Varianz an der Deklarationsstelle: in und out“ ist hier im Web kostenlos zu lesen. Um sie interaktiv zu üben (integrierter Code-Editor und 24/7 KI-Tutor) und den Rest des Kotlin Academy-Kurses freizuschalten, upgrade auf CoddyKit PRO. Der Kotlin Academy-Kurs umfasst insgesamt 4 Lektionen.
Was lerne ich in „Varianz an der Deklarationsstelle: in und out“?
Verwenden Sie kovariante (out) und kontravariante (in) Typparameter korrekt. Du übst Kotlin Academy mit praktischem Code, den du direkt im Browser ausführst, und ein 24/7 KI-Tutor beantwortet deine Fragen während du die Lektion bearbeitest.
Brauche ich Erfahrung, um Kotlin Academy zu starten?
Keine Vorkenntnisse erforderlich. Kotlin Academy auf CoddyKit ist für Anfänger bis fortgeschrittene Lernende strukturiert, sodass du hier starten oder von Anfang an beginnen und in deinem eigenen Tempo voranschreiten kannst. Dies ist Lektion 2 von 4.
Wie lange dauert die Lektion „Varianz an der Deklarationsstelle: in und out“?
Die meisten CoddyKit-Lektionen dauern etwa 5–10 Minuten. Jede ist kompakt und interaktiv, sodass du stetig Fortschritte machst und genau dort weitermachst, wo du aufgehört hast – im Web und in der App.
Kann ich in dieser Kotlin Academy-Lektion Code schreiben und ausführen?
Ja. Jede Kotlin Academy-Lektion enthält einen integrierten Code-Editor, sodass du echten Code direkt in deinem Browser schreibst und ausführst und sofort KI-Feedback erhältst — ohne lokale Einrichtung erforderlich.
Alle Lektionen in diesem Kurs
- Generische Funktionen und Typbeschränkungen mit where
- Varianz an der Deklarationsstelle: in und out
- Star Projection und wann * verwendet wird
- Type Erasure und reifizierte Typparameter