Eine Funktion, die eine Liste sortiert, muss nicht wissen, ob die Liste Int, Double
oder String enthält — sie muss nur wissen, dass sich zwei Elemente vergleichen lassen.
Ohne Generics bräuchte man für jeden Typ eine eigene Funktion mit identischer Logik:
fun sortiereGanzzahlen(werte: List<Int>): List<Int> { /* ... */ }
fun sortiereKommazahlen(werte: List<Double>): List<Double> { /* ... */ }
fun sortiereText(werte: List<String>): List<String> { /* ... */ }
Das ist reine Kopierarbeit. Generics lösen das, indem sie einen Typ durch einen
Type Parameter ersetzen — einen Platzhalter wie T, der erst beim Aufruf bzw. beim
Erzeugen einer Instanz durch einen konkreten Typ ersetzt wird.
Der Typparameter steht in spitzen Klammern direkt vor der Parameterliste:
fun <T> erstesElement(liste: List<T>): T? = liste.firstOrNull()
fun main() {
val referenznummern = listOf("A-100", "A-101")
val mengen = listOf(5, 12, 3)
println(erstesElement(referenznummern)) // A-100
println(erstesElement(mengen)) // 5
}
erstesElement funktioniert für jede Liste, unabhängig vom Elementtyp. Beim Aufruf muss
der konkrete Typ nicht angegeben werden — Kotlin leitet ihn aus dem übergebenen Argument
ab. Explizit ginge es auch: erstesElement<Int>(mengen), ist hier aber unnötig.
Genauso lassen sich Klassen generisch machen. Ein Lagerfach, das irgendeine Ware aufnehmen kann, muss nicht wissen, ob dort ein Buch oder ein Ersatzteil liegt:
class Lagerfach<T>(val inhalt: T) {
fun ausgeben(): T = inhalt
}
fun main() {
val fachFuerBuecher = Lagerfach("Kotlin Grundlagen") // T wird zu String
val fachFuerZahlen = Lagerfach<Int>(42) // T explizit als Int
println(fachFuerBuecher.ausgeben())
println(fachFuerZahlen.ausgeben())
}
Auch hier reicht Typinferenz meistens aus (Lagerfach("Kotlin Grundlagen")). Die explizite
Form Lagerfach<Int>(42) ist nur nötig, wenn sich der Typ nicht eindeutig aus den
Argumenten ergibt, oder wenn sie der Lesbarkeit dient.
Ein Type Parameter kann dabei mehr als eine Verwendung im Rumpf haben — als
Konstruktor-Parameter, als Rückgabetyp einer Methode, oder beides gleichzeitig, wie in
Lagerfach. Wie man diesen Platzhalter einschränkt, damit er nur Typen mit bestimmten
Fähigkeiten akzeptiert, ist Thema der nächsten Lektion.
Weiterlesen: Generics: in, out, where – Generic functions and classes
Lies den Code genau durch, bevor du ihn ausführst.
class Lagerfach<T>(val inhalt: T)
fun main() {
val fach1 = Lagerfach(42)
val fach2 = Lagerfach("Netzwerkkabel")
println(fach1.inhalt)
println(fach2.inhalt)
}
Was gibt dieses Programm aus? Muss man den Typ `T` beim Aufruf von `Lagerfach(...)` irgendwo explizit angeben, damit das kompiliert?
Schreib eine generische Funktion letztesElement, die das letzte Element einer Liste beliebigen Typs zurückgibt — oder null, wenn die Liste leer ist. Die Funktion soll ohne Einschränkung an T für jede Liste funktionieren, egal ob List<String>, List<Int> oder eine Liste eigener Klassen.
Bevor du einen Hint bekommst: Was hast du schon probiert, und woran hängt es genau?
1. Wofür steht der Typparameter T in `class Lagerfach<T>(val inhalt: T)`?
2. Ohne Generics müsste man für `Lagerfach<Int>`, `Lagerfach<String>` und `Lagerfach<Bestellung>` jeweils eine eigene Klasse schreiben. Was ist der Hauptgrund, das mit Generics zu vermeiden?
3. Muss man bei `val box = Lagerfach(42)` den Typ `T` explizit als `Lagerfach<Int>(42)` schreiben?