Menu
Coddy logo textTech

Generische Klassen

Teil des Abschnitts Objektorientierte Programmierung der Kotlin-Journey von Coddy. Lektion 38 von 57.

Eine Klasse kann ebenfalls Typparameter haben. Box<T> enthält einen Wert eines beliebigen Typs, und jede Instanz legt den Typ fest: Eine Box<Int> akzeptiert nur Int-Werte:

class Box<T>(var value: T) {
    fun describe() = "box with $value"
}

Innerhalb von main:

val numberBox = Box(42)
val textBox = Box("hello")
numberBox.value += 1
println(numberBox.describe())
println(textBox.describe())
// numberBox.value = "x"   // Fehler: ein String ist kein Int

Ausgabe:

box with 43
box with hello

Container sind die typischen generischen Klassen. Eine Stack<T>-Implementierung funktioniert für jeden Elementtyp, und der Compiler überprüft, was hineingelangt und herauskommt:

class Stack<T> {
    private val items = mutableListOf<T>()
    val size: Int
        get() = items.size

    fun push(item: T) { items.add(item) }
    fun pop(): T? = items.removeLastOrNull()
    fun peek(): T? = items.lastOrNull()
}

Innerhalb von main:

val stack = Stack<String>()
stack.push("a")
stack.push("b")
println(stack.pop())
println(stack.peek())
println(stack.size)

Ausgabe:

b
a
1

Das Typargument wird aus den Konstruktorargumenten abgeleitet, sofern welche vorhanden sind, wie bei Box(42). Andernfalls gib es an: Stack<String>(). Instanzen mit unterschiedlichen Typargumenten sind unterschiedliche Typen, daher wird ein Stack<String> niemals mit einem Stack<Int> verwechselt. Funktionen einer generischen Klasse können auch eigene Typparameter hinzufügen:

class Box<T>(val value: T) {
    fun <R> map(f: (T) -> R): Box<R> = Box(f(value))
}

Innerhalb von main:

val length = Box("kotlin").map { it.length }
println(length.value + 1)

Ausgabe:

7

Generische Schnittstellen folgen denselben Regeln. Eine Klasse kann eine solche Schnittstelle generisch oder für einen bestimmten Typargumenttyp implementieren:

interface Repository<T> {
    fun add(item: T)
    fun all(): List<T>
}
class MemoryRepository<T> : Repository<T> {
    private val items = mutableListOf<T>()
    override fun add(item: T) { items.add(item) }
    override fun all() = items.toList()
}
class TagRepository : Repository<String> {
    private val tags = sortedSetOf<String>()
    override fun add(item: String) { tags.add(item.lowercase()) }
    override fun all() = tags.toList()
}

Innerhalb von main:

val ids = MemoryRepository<Int>()
ids.add(3); ids.add(1)
val tags = TagRepository()
tags.add("Kotlin"); tags.add("JVM")
println("${ids.all()} ${tags.all()}")

Ausgabe:

[3, 1] [jvm, kotlin]
challenge icon

Aufgabe

Einfach

Schreibe die generische Klasse History<T>(limit), die sich die letzten limit Elemente merkt. add(item) fügt ein Element hinzu und entfernt das älteste, wenn mehr als limit Elemente vorhanden sind. latest ist das neueste Element oder null, und all() gibt die Elemente vom ältesten bis zum neuesten als schreibgeschützte Liste zurück. Schreibe dann die Erweiterung History<Int>.average(), die nur für Verläufe von Zahlen existiert und den ganzzahligen Durchschnitt der gespeicherten Zahlen zurückgibt (bei leerem Verlauf 0).

Der bereitgestellte Code liest das Limit, eine Zeile mit Wörtern und eine Zeile mit Zahlen ein. Er füllt ein History<String> und ein History<Int> und gibt die gespeicherten Wörter, das neueste Wort, die gespeicherten Zahlen, ihren Durchschnitt und empty latest: null aus.

Dein Code gehört in History.kt. Main.kt enthält den bereitgestellten Eingabe-/Ausgabecode und darf nicht bearbeitet werden.

Probier es selbst

fun main() {
    // Bereitgestellter Ein-/Ausgabe-Code: belasse ihn wie er ist
    val input = generateSequence(::readLine).toList()
    val limit = input[0].toInt()
    val words = History<String>(limit)
    for (w in input[1].split(" ")) words.add(w)
    val numbers = History<Int>(limit)
    for (n in input[2].split(" ")) numbers.add(n.toInt())
    println("words: ${words.all()}")
    println("latest word: ${words.latest}")
    println("numbers: ${numbers.all()}")
    println("average: ${numbers.average()}")
    println("empty latest: ${History<Char>(3).latest}")
}
quiz iconTeste dich selbst

Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.

Alle Lektionen in Objektorientierte Programmierung

Übe selbstständig: Kotlin-Playground