ProgrammierungBackend-Entwickler

Wie sind Generics in Kotlin implementiert? Welche Einschränkungen gibt es, wie funktioniert Invarianz, Kovarianz und Kontravarianz und wie unterscheiden sie sich von Generics in Java? Geben Sie Beispiele für die Verwendung.

Bestehen Sie Vorstellungsgespräche mit dem Hintsage-KI-Assistenten

Antwort

Generics in Kotlin ermöglichen die Erstellung universeller und typensicherer Datenstrukturen und Funktionen. Das Hauptmerkmal: Kotlin implementiert das System der generischen Typen auf Compiler-Ebene, ähnlich wie Java, aber mit einer strengeren Typisierung und einer erweiterten Variance-Syntax (Kovarianz und Kontravarianz).

Einschränkungen von Generics:

  • Typparameter sind standardmäßig invariant.
  • Es können keine Instanzen von Typparametern erstellt werden (T() ist verboten).
  • Es gibt keinen Zugriff auf Informationen über generische Typen zur Laufzeit (Type Erasure).

Variance:

  • Kovarianz (out T): Ermöglicht die Verwendung von Untertypen.
  • Kontravarianz (in T): Ermöglicht die Verwendung von Supertypen.
  • Invarianz: Typ ohne Variation-Modifikator.

Beispiel für Kovarianz:

interface Producer<out T> { fun produce(): T }

Beispiel für Kontravarianz:

interface Consumer<in T> { fun consume(item: T) }

Unterschied zu Java:

  • Die Syntax ist klarer und prägnanter (out anstelle von ? extends, in anstelle von ? super).
  • Es gibt keine Wildcard-Typen (?, nur in/out).
  • Die Erstellung von Instanzen eines generischen Parameters ist verboten.

Trickfrage

Frage: "Kann man in Kotlin ein Array von Arrays (Array<Array<Int>>) als Array<out Array<Int>> deklarieren und was passiert, wenn man versucht, in solch ein Array zu schreiben?"

Antwort: Ja, man kann es als Array<out Array<Int>> deklarieren, aber dieses Array wird nur lesbar (read-only):

val arr: Array<out Array<Int>> = Array(1) { Array(1) { 0 } } arr[0] = arrayOf(1, 2, 3) // Kompilierungsfehler!

Ein Versuch, einen Wert zu schreiben, führt zu einem Fehler – ein generisches Array mit out-Parameter erlaubt keine Elementeinschreibungen, da dies die Typensicherheit verletzen würde.

Beispiele für reale Fehler aufgrund mangelnden Wissens über die Feinheiten des Themas


Geschichte

Im Team versuchten sie, ein Array von generischen Objekten mit dem Typ des out-Parameters zu erstellen und dann Werte über set(index, value) hineinzulegen. Der Code wurde kompiliert, verursachte jedoch einen Fehler zur Laufzeit, und mehrere Funktionen funktionierten nicht.


Geschichte

Einmal bei der Migration einer Bibliothek von Java nach Kotlin wurden Wildcard-Typen (? extends ...), und in Kotlin wurden die Typen einfach kopiert, ohne auf out/in zu ändern. Ergebnis – die Kompilierung schlug fehl, und beim "Durchlaufen" trat ein Fehler zur Laufzeit auf, was den Prozess des Debuggens erschwerte.


Geschichte

Sie verwendeten in/out Variance mit einer benutzerdefinierten Klasse, vertauschten jedoch die Modifikatoren, indem sie das Interface Stack<in T> anstelle von Stack<out T> deklarierten. Dies führte dazu, dass Elemente aus dem Stack nicht zurückgegeben werden konnten: Die Methodensignatur verletzte den system out/in Vertrag.