Deklarationsseitige Varianz
in und out
Deklarationsseitige Varianz 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
Ist eine List<String> eine Art von List<Any>? Manchmal ja, manchmal nein. Varianz beschreibt, wie generische Typen zueinander in Beziehung stehen, wenn ihre Typparameter in Beziehung stehen. Kotlin verwendet out und in, um dies auszudrücken.
Standardmäßig invariant
Generics sind standardmäßig invariant: Box<String> ist KEINE Box<Any>, obwohl String ein Subtyp von Any ist. Dadurch werden unsichere Operationen verhindert.
class Box<T>(val value: T)
fun main() {
val strBox = Box("hi")
// val anyBox: Box<Any> = strBox // would not compile
println(strBox.value)
}Kovarianz mit out
Wenn Sie einen Typparameter mit out markieren, wird er kovariant: Producer<String> wird zu einem Subtyp von Producer<Any>. Der Typ darf nur an Ausgabepositionen, also als Rückgabetyp, vorkommen.
class Producer<out T>(private val item: T) {
fun produce(): T = item
}
fun main() {
val strProducer: Producer<String> = Producer("hello")
val anyProducer: Producer<Any> = strProducer
println(anyProducer.produce())
}Warum out sicher ist
Wenn eine Klasse T ausschließlich erzeugt, also zurückgibt, und nie konsumiert, ist es sicher, einen Producer<String> wie einen Producer<Any> zu behandeln, denn jeder zurückgegebene String ist auch ein Any.
out in der Standardbibliothek
Kotlins schreibgeschütztes List<out E> ist kovariant. Deshalb kann eine List<String> überall verwendet werden, wo eine List<Any> erwartet wird.
fun printAll(items: List<Any>) {
items.forEach { println(it) }
}
fun main() {
val names: List<String> = listOf("Ann", "Bob")
printAll(names)
}Kontravarianz mit in
Wenn Sie einen Typparameter mit in markieren, wird er kontravariant: Consumer<Any> wird zu einem Subtyp von Consumer<String>. Der Typ darf nur an Eingabepositionen, also als Parameter, vorkommen.
class Consumer<in T> {
fun consume(item: T) {
println("Consumed $item")
}
}
fun main() {
val anyConsumer: Consumer<Any> = Consumer()
val strConsumer: Consumer<String> = anyConsumer
strConsumer.consume("hi")
}Warum in sicher ist
Ein Consumer, der Any akzeptiert, kann selbstverständlich auch einen String akzeptieren. Daher kann ein Consumer<Any> überall dort eingesetzt werden, wo ein Consumer<String> benötigt wird.
Die PECS-Merksatz
Merken Sie sich Producer Extends, Consumer Super oder in Kotlin: out für Erzeuger (Ausgabe) und in für Consumer (Eingabe). Ein Typ, der T ausschließlich ausgibt, verwendet out; ein Typ, der T ausschließlich als Eingabe akzeptiert, verwendet in.
in und out kombinieren
Ein Typparameter, der sowohl für die Eingabe als auch für die Ausgabe verwendet wird, etwa in einer veränderlichen Liste, muss invariant bleiben. Function-Typen zeigen beides: Parameter sind in, der Rückgabewert ist out.
fun main() {
val f: (Number) -> Int = { it.toInt() }
val g: (Int) -> Number = f
println(g(3.0.let { Number::class; 5 }))
}Varianz an der Deklarationsstelle bedeutet eine zentrale Festlegung
Die Varianz an der Deklarationsstelle wird einmalig an der Klasse selbst mit in/out festgelegt. Jede Verwendung der Klasse erhält diese Varianz dann automatisch. In Java müssen Sie dagegen bei jeder Verwendung Wildcards wiederholen.
in oder out auswählen
Fragen Sie sich: Erzeugt diese Klasse ausschließlich T, sodass Sie out verwenden, akzeptiert sie ausschließlich T als Eingabe, sodass Sie in verwenden, oder tut sie beides, sodass sie invariant bleibt? Wenn der Modifikator zur Aufgabe passt, werden Ihre generischen Typen flexibel und sicher.
Kurzer Test
Was zeigt der Modifikator out an?
Zusammenfassung
Generics sind standardmäßig invariant. Verwenden Sie out für Kovarianz, wenn ein Typ ausschließlich Werte erzeugt, und in für Kontravarianz, wenn er ausschließlich Werte konsumiert. Denken Sie daran: Erzeuger verwenden out, Consumer verwenden in. Als Nächstes wenden Sie Varianz an der Verwendungsstelle mit Typprojektionen an.
Häufig gestellte Fragen
Ist die Lektion „Deklarationsseitige Varianz“ kostenlos?
Ja — der vollständige Text von „Deklarationsseitige Varianz“ 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 „Deklarationsseitige Varianz“?
in und out 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 „Deklarationsseitige Varianz“?
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 Klassen
- Deklarationsseitige Varianz
- Verwendungsseitige Varianz
- Einschränkungen für generische Typen