Varianz mit in und out
Teil des Abschnitts Objektorientierte Programmierung der Kotlin-Journey von Coddy. Lektion 40 von 57.
Eine List<String> kann dort übergeben werden, wo eine List<Any> erwartet wird, weil eine List Elemente nur ausgibt. Eine MutableList<String> kann das nicht: Code, der sie als MutableList<Any> sieht, könnte ein Int-Element zu einer Liste von Strings hinzufügen:
fun describeAll(items: List<Any>) = items.joinToString { it::class.simpleName ?: "?" }
fun addNumber(items: MutableList<Any>) { items.add(42) }Innerhalb von main:
val words = listOf("a", "b")
println(describeAll(words))
val mixed = mutableListOf<Any>("x")
addNumber(mixed)
println(mixed)
// addNumber(mutableListOf("a")) // Fehler: MutableList<String> ist keine MutableList<Any>Ausgabe:
String, String
[x, 42]out T macht eine generische Klasse kovariant: Sie produziert nur T-Werte als Rückgabetypen und nimmt sie niemals entgegen. Daher gilt ein Producer<Cat> als Producer<Animal>:
open class Animal(val name: String)
class Cat(name: String) : Animal(name)
class Producer<out T>(private val item: T) {
fun get(): T = item
}
fun greet(p: Producer<Animal>) = "hello ${p.get().name}"Innerhalb von main:
val cats: Producer<Cat> = Producer(Cat("Tom"))
println(greet(cats))Ausgabe:
hello Tomin T macht es kontravariant: Es konsumiert nur T-Werte als Parameter. Ein Consumer, der beliebige Animal-Werte verarbeitet, kann anstelle eines Consumers für Cat verwendet werden:
open class Animal(val name: String)
class Cat(name: String) : Animal(name)
interface Feeder<in T> {
fun feed(animal: T): String
}
class AnimalFeeder : Feeder<Animal> {
override fun feed(animal: Animal) = "fed ${animal.name}"
}
fun feedCats(cats: List<Cat>, feeder: Feeder<Cat>) = cats.map { feeder.feed(it) }Innerhalb von main:
println(feedCats(listOf(Cat("Tom"), Cat("Kit")), AnimalFeeder()))Ausgabe:
[fed Tom, fed Kit]Der Compiler erzwingt die Richtung: Ein Typparameter out kann nicht der Typ eines Methodenparameters sein, und ein in-Typparameter kann nicht zurückgegeben werden. Als Faustregel gilt: Produzent out, Konsument in. Die Standardbibliothek verfährt genauso: List<out E>, Comparator<in T>. Ohne eines der beiden Schlüsselwörter ist ein Typparameter invariant, wie bei MutableList<E>, das sowohl liest als auch schreibt:
class Producer<out T>(private val item: T) {
fun get(): T = item // in Ordnung: T geht raus
// fun set(x: T) { } // Fehler: T ist als out deklariert
}
interface Feeder<in T> {
fun feed(animal: T): String // in Ordnung: T kommt rein
// fun last(): T // Fehler: T ist als in deklariert
}Varianz kann auch dort geschrieben werden, wo ein Typ verwendet wird. MutableList<in Int> akzeptiert jede veränderbare Liste, die Int-Werte aufnehmen kann, beispielsweise eine MutableList<Number>, und MutableList<out Number> erlaubt nur das Lesen:
fun fillWith(target: MutableList<in Int>, n: Int) {
for (i in 1..n) target.add(i)
}
fun total(source: MutableList<out Number>) = source.sumOf { it.toDouble() }Innerhalb von main:
val numbers = mutableListOf<Number>(0.5)
fillWith(numbers, 3)
println(numbers)
println(total(mutableListOf(1, 2, 3)))Ausgabe:
[0.5, 1, 2, 3]
6.0Aufgabe
EinfachZwei Schnittstellen bewegen Elemente: Source<out T> erzeugt Elemente mit next() (und gibt null zurück, wenn sie leer ist), und Sink<in T> verarbeitet sie mit accept(item). Sie werden bereitgestellt. Schreibe ListSource<T>(items), das die Elemente der Liste einzeln und anschließend null zurückgibt; Collector<T>, das akzeptierte Elemente in einer schreibgeschützten items-Liste aufbewahrt; und Counter, einen Sink<Any>, der alles zählt, was er akzeptiert. Schreibe anschließend pipe(from, vararg to), das jedes Element der Quelle in jeden Senken überträgt und zurückgibt, wie viele Elemente es übertragen hat.
Der bereitgestellte Code leitet eine Zeile mit Wörtern in einen Collector<String> und eine Zeile mit Zahlen in einen Collector<Number> und zusätzlich beide Male in einen Counter. Das funktioniert aufgrund von out und in: Ein ListSource<Int> ist ein Source<Number>, und ein Sink<Any> ist ein Sink<String>. Er gibt die Anzahlen, beide Sammlungen, counted: 7 und after the end: null aus.
Dein Code gehört in ListSource.kt, Collector.kt, Counter.kt, Pipe.kt, Source.kt und Sink.kt. Main.kt enthält den bereitgestellten Eingabe-/Ausgabecode und kann nicht bearbeitet werden.
Probier es selbst
fun main() {
// Gelieferter Ein-/Ausgabe-Code: belasse ihn wie er ist
val input = generateSequence(::readLine).toList()
val words = ListSource(input[0].split(" "))
val ints = ListSource(input[1].split(" ").map { it.toInt() })
val counter = Counter()
val allWords = Collector<String>()
val numbers = Collector<Number>()
println("words moved: " + pipe(words, allWords, counter))
println("numbers moved: " + pipe(ints, numbers, counter))
println(allWords.items)
println(numbers.items)
println("counted: ${counter.count}")
println("after the end: ${words.next()}")
}
Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.
Alle Lektionen in Objektorientierte Programmierung
1Klassen und Objekte
Arbeiten mit DateienKlassen definierenPrimärkonstruktoreninit-Blöcke und ValidierungSekundärkonstruktorenRückblick – Bibliotheksausweis4Vererbung
Offene Klassen und UnterklassenÜberschreiben von Membernsuper und KonstruktorenAbstrakte KlassenRückblick – Mitarbeiterverzeichnis2Eigenschaften und Kapselung
Benutzerdefinierte ZugriffsmethodenSichtbarkeitsmodifikatorenPrivate Setterlazy und lateinitWiederholung – Bankkonto5Schnittstellen
Schnittstellen deklarierenStandardmethodenMehrere SchnittstellenSchnittstellen als TypenRückblick – Zahlungsmethoden8Generics
Generische FunktionenGenerische KlassenTypbeschränkungenVarianz mit in und outRückblick – Prioritätswarteschlange11Projekt: Bibliotheksverwaltung
Bücher und MitgliederBücher ausleihenÜbe selbstständig: Kotlin-Playground