Was verbirgt sich hinter dem Wort out in Kotlin? Wir beantworten die häufigsten Fragen
\n\nWenn Sie jemals Generics in Kotlin geschrieben haben und auf einen unverständlichen Kompilierungsfehler gestoßen sind, haben Sie wahrscheinlich das Wort out gesehen. Dieser kleine Modifikator sorgt selbst bei erfahrenen Entwicklern für viele Diskussionen. Wir haben die häufigsten Fragen von Stack Overflow gesammelt und in eine verständliche Anleitung verwandelt. Heute erfahren Sie, was out ist, wofür es benötigt wird und wie Sie sich in der Kovarianz nicht verlieren.
Lassen Sie es uns ein für alle Mal klären – ohne langweilige Theorie, nur mit lebendigem Code und Praxis.
\n\nWarum hat Kotlin out erfunden? (Ein kurzer Ausflug in die Geschichte)
\n\nIn Java gibt es Wildcards – das Fragezeichen ? extends T. Es bedeutet, dass eine Sammlung Objekte vom Typ T oder dessen Untertypen enthalten kann. Das Problem ist, dass Java Sie nicht daran hindert, versehentlich ein falsches Objekt in eine solche Sammlung zu legen – Sie erhalten den Fehler erst zur Laufzeit. Kotlin hat dieses Problem auf Typebene gelöst.
Das Schlüsselwort out in Kotlin ist eine Deklaration von Kovarianz. Wenn Sie Source<out T> schreiben, bedeutet das: „Ich kann Objekte vom Typ T aus dieser Source lesen, aber nicht in sie schreiben“. Der Compiler überwacht dies streng.
Frage 1: Was ist der Unterschied zwischen out und in?
\n\nDie häufigste Frage auf Stack Overflow. Stellen wir uns eine Kiste vor:
\n\n- \n
out– eine Kiste, aus der man nur Dinge herausnehmen kann (produziert). \n in– eine Kiste, in die man nur Dinge hineinlegen kann (konsumiert). \n
Beispiel:
\n\n// Kovarianz: können lesen, aber nicht schreiben\ninterface Producer<out T> {\n fun produce(): T\n}\n\n// Kontravarianz: können schreiben, aber nicht lesen\ninterface Consumer<in T> {\n fun consume(item: T)\n}\n\nfun main() {\n val stringProducer: Producer<String> = object : Producer<String> {\n override fun produce(): String = "Hallo"\n }\n \n val anyProducer: Producer<Any> = stringProducer // Funktioniert! out erlaubt es\n println(anyProducer.produce())\n}\n\n\nBeachten Sie: Producer<out T> erlaubt es, Producer<String> einer Variable vom Typ Producer<Any> zuzuweisen. Ohne out wäre das unmöglich.
Frage 2: Warum verbietet Kotlin fun set(value: T) bei out?
\n\nDas ist eine klassische Falle. Wenn Sie out deklariert haben, erlaubt der Compiler keine Funktionen, die T als Parameter akzeptieren. Warum?
Stellen Sie sich vor, Sie haben eine MutableList<out Number>. Sie könnten ein Int hineinlegen, aber der Compiler weiß nicht, um welche Liste es sich tatsächlich handelt. Vielleicht ist es eine MutableList<Double>? Wenn das Schreiben erlaubt wäre, würden Sie die Typen brechen. out garantiert Sicherheit: Sie können nur lesen.
// Wird nicht kompiliert!\ninterface BadList<out T> {\n fun get(index: Int): T // OK\n fun add(item: T) // Fehler! T befindet sich in einer in-Position\n}\n\n\nLösung: Wenn Sie sowohl lesen als auch schreiben müssen, verwenden Sie in und out getrennt oder einen invarianten Typ (ohne Modifikator).
\n\nFrage 3: Wie hängt out mit dem Wildcard ? extends aus Java zusammen?
\n\nDirekte Entsprechung: Source<out T> in Kotlin = Source<? extends T> in Java. Aber es gibt einen wichtigen Unterschied:
- \n
- In Java ist das Wildcard eine Verwendung des Typs (use-site variance). \n
- In Kotlin ist
outeine Deklaration des Typs (declaration-site variance). \n
Beispiel in Java:
\n\n// Java: Wildcard bei der Variablendeklaration verwenden\nList<? extends Number> numbers = new ArrayList<Integer>();\nnumbers.a