Generics in Kotlin: warum sie wichtig sind und wie man sie in der Praxis einsetzt
Generics (Verallgemeinerungen) sind eine der Schlüsselfunktionen der Sprache Kotlin, die es ermöglicht, flexibleren, typsicheren und wiederverwendbaren Code zu schreiben. Wenn Sie jemals List<String> oder Map<Int, User> geschrieben haben, sind Sie bereits auf Generics gestoßen. Aber warum werden sie eigentlich benötigt und wie holt man das Maximum aus ihnen heraus? In diesem Artikel werden wir die Theorie durchgehen und mit praktischen, realen Beispielen fortfahren.
Das Problem, das Generics lösen
Stellen Sie sich vor, Sie müssen eine Funktion schreiben, die das erste Element aus einer Liste zurückgibt. Ohne Generics müssten Sie für jeden Datentyp eine separate Funktion schreiben:
fun getFirstString(list: List<String>): String? = list.firstOrNull()fun getFirstInt(list: List<Int>): Int? = list.firstOrNull()fun getFirstUser(list: List<User>): User? = list.firstOrNull()Dies führt zu Code-Duplizierung. Die Verwendung von Any würde das Problem lösen, aber Sie verlieren die Typinformationen zur Kompilierzeit:
fun getFirst(list: List<Any>): Any? = list.firstOrNull()
// Man müsste eine Typumwandlung durchführenval name: String? = getFirst(strings) as? StringGenerics lösen dieses Problem elegant:
fun <T> getFirst(list: List<T>): T? = list.firstOrNull()
// Der Compiler kennt den Typ!val name: String? = getFirst(listOf("Alice", "Bob"))val age: Int? = getFirst(listOf(25, 30))Jetzt arbeitet eine Funktion mit jedem Typ und behält dabei die vollständige Typsicherheit.
Grundlagen von Generics in Kotlin: Syntax und Deklaration
Verallgemeinerungen werden mit spitzen Klammern <T> deklariert. Der Buchstabe T ist nur eine Konvention; es können beliebige Namen verwendet werden: E (Element), K (Schlüssel), V (Wert).
Generic-Klassen
class Box<T>(val value: T) { fun getContent(): T = value}
fun main() { val stringBox = Box("Hallo") val intBox = Box(42) println(stringBox.getContent()) // Hallo println(intBox.getContent()) // 42}Generic-Funktionen
fun <T> printItem(item: T) { println("Element: $item")}
// Man kann den Typ explizit angebenprintItem<String>("Kotlin")// Oder sich auf die Typinferenz verlassenprintItem(100)Generic-Schnittstellen
interface Repository<T> { fun getById(id: Int): T fun save(item: T): Boolean}
class UserRepository : Repository<User> { override fun getById(id: Int): User { /* ... */ } override fun save(item: User): Boolean { /* ... */ }}Varianz: in, out und der Stern *
Eine der leistungsstärksten Funktionen von Kotlin ist die Varianz. Sie beantwortet die Frage: Kann List<String> ein Subtyp von List<Any> sein?
Kovarianz (out)
Verwenden Sie out, wenn Ihr generischer Typ nur zurückgegeben wird (Producer). Zum Beispiel ist List<T> kovariant:
// Sie können List<String> einer Variable vom Typ List<Any> zuweisenval strings: List<String> = listOf("a", "b")val anyList: List<Any> = strings // OK!
// Sie können jedoch keine Elemente hinzufügen// anyList.add(1) // Kompilierfehler!Ein eigenes Beispiel:
class Producer<out T>(private val value: T) { fun produce(): T = value}
fun main() { val stringProducer: Producer<String> = Producer("Hallo") val anyProducer: Producer<Any> = stringProducer // Funktioniert! println(anyProducer.produce())}Kontravarianz (in)
Verwenden Sie in, wenn der Typ nur entgegengenommen wird (Consumer). Zum Beispiel Comparable<T>:
Blogs
Buchempfehlungen
Lesen Sie auch
Extends Thread oder implements Runnable: Wie Sie das Vorstellungsgespräch nicht vermasseln
Arbeiten mit Datum und Uhrzeit in Swift: Fortgeschrittene Techniken
Vergleich von Slices in Go: Optimierung und Leistung
Arbeit mit Datenbanken in C++: PostgreSQL und MongoDB
YouTube API: Wie man ein Video-Thumbnail erhält und nicht auf die Nase fällt