Einführung in Generics in Kotlin
Generics (generische Typen) sind eine der wichtigsten Funktionen statisch typisierter Programmiersprachen, einschließlich Kotlin. Sie ermöglichen das Schreiben von flexiblem, wiederverwendbarem Code, der mit verschiedenen Datentypen arbeitet, während die strenge Typsicherheit zur Kompilierzeit erhalten bleibt.
Stellen Sie sich vor, Sie müssen eine Datenstruktur zum Speichern einer Liste von Elementen erstellen. Ohne Generics müssten Sie separate Klassen für jeden Typ schreiben (IntList, StringList, UserList). Mit Generics reicht eine einzige Klasse MyList<T>, die mit jedem Typ funktioniert.
// Ohne Generics — Code muss dupliziert werdenclass IntList(private val items: MutableList<Int>)class StringList(private val items: MutableList<String>)
// Mit Generics — eine Klasse für alle Typenclass GenericList<T>(private val items: MutableList<T>) { fun add(item: T) { items.add(item) } fun get(index: Int): T = items[index]}In diesem Artikel werden wir im Detail besprechen, wie Generics in Kotlin funktionieren, ihre Syntax, Anwendungsfälle und fortgeschrittene Konzepte wie Kovarianz und Kontravarianz betrachten.
Grundlagen der Arbeit mit Generics
Deklaration generischer Klassen und Funktionen
Um eine generische Klasse zu deklarieren, geben Sie einfach einen oder mehrere Typparameter in spitzen Klammern nach dem Klassennamen an:
class Box<T>(var value: T) { fun getValue(): T = value fun setValue(newValue: T) { value = newValue }}
// Verwendungval intBox = Box<Int>(42)val stringBox = Box<String>("Hallo")
// Kotlin kann den Typ ableiten, daher kann man kürzer schreibenval anotherBox = Box("Automatische Typinferenz")Funktionen können ebenfalls generisch sein. Der Typparameter wird vor dem Funktionsnamen angegeben:
fun <T> printItem(item: T) { println("Element: $item")}
fun <T> createPair(first: T, second: T): Pair<T, T> { return Pair(first, second)}
// VerwendungprintItem(123)printItem("Text")val pair = createPair("A", "B")Type Bounds (Typbeschränkungen)
Manchmal muss man die Typen einschränken, die in Generics verwendet werden können. Dafür werden obere Grenzen (upper bounds) mit dem Schlüsselwort : Type verwendet:
// Nur Typen, die Comparable implementierenfun <T : Comparable<T>> findMax(a: T, b: T): T { return if (a > b) a else b}
// Mehrere Einschränkungen mit wherefun <T> processItem(item: T) where T : CharSequence, T : Comparable<T> { println("Länge: ${item.length}") println("Verglichen: ${item.compareTo(item)}")}
// Verwendungprintln(findMax(10, 20)) // 20println(findMax("Apfel", "Banane")) // "Banane"// findMax(10, "Text") // Kompilierfehler!Varianz: in, out und Typ-Projektionen
Eines der komplexesten, aber wichtigsten Themen bei Generics ist die Varianz. Sie bestimmt, wie generische Typen bei Vererbung zueinander in Beziehung stehen.
Invarianz (Standard)
Standardmäßig sind Generics in Kotlin invariant. Das bedeutet, dass Box<Number> und Box<Int> keine Subtypen voneinander sind, selbst wenn Int ein Subtyp von Number ist:
val intBox: Box<Int> = Box(42)// val numBox: Box<Number> = intBox // Kompilierfehler!Kovarianz (out)
Der Modifikator out macht den Typ kovariant. Das bedeutet, dass Box<Int> zu einem Subtyp von Box<Number> wird. Es wird verwendet, wenn der Typ T nur in Ausgabepositionen (Rückgabewerte) verwendet wird:
class Producer<out T>(private val item: T) { fun produce(): T = item // T nur am Ausgang}