0Pricing
Kotlin Academy · Lektion

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

  1. Generische Funktionen und Klassen
  2. Deklarationsseitige Varianz
  3. Verwendungsseitige Varianz
  4. Einschränkungen für generische Typen
← Zurück zu Kotlin Academy