Varianza con in e out
Fa parte della sezione Programmazione orientata agli oggetti del percorso Kotlin di Coddy. Lezione 40 di 57.
Un List<String> può essere passato dove è atteso un List<Any>, perché un List fornisce solo gli elementi. Un MutableList<String> non può esserlo: il codice che lo vede come MutableList<Any> potrebbe aggiungere un Int a una lista di stringhe:
fun describeAll(items: List<Any>) = items.joinToString { it::class.simpleName ?: "?" }
fun addNumber(items: MutableList<Any>) { items.add(42) }All'interno di main:
val words = listOf("a", "b")
println(describeAll(words))
val mixed = mutableListOf<Any>("x")
addNumber(mixed)
println(mixed)
// addNumber(mutableListOf("a")) // error: MutableList<String> is not a MutableList<Any>Output:
String, String
[x, 42]out T rende una classe generica covariante: produce solo valori di T, come tipi restituiti, e non li accetta mai. Quindi un Producer<Cat> è considerato un 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}"All'interno di main:
val cats: Producer<Cat> = Producer(Cat("Tom"))
println(greet(cats))Output:
hello Tomin T la rende controvariante: consuma solo valori di T, come parametri. Un consumatore che gestisce qualsiasi Animal può sostituire un consumatore di Cat:
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) }All'interno di main:
println(feedCats(listOf(Cat("Tom"), Cat("Kit")), AnimalFeeder()))Output:
[fed Tom, fed Kit]Il compilatore impone la direzione: un parametro di tipo out non può essere il tipo di un parametro di metodo e uno in non può essere restituito. La regola pratica è produttore out, consumatore in. La libreria standard fa lo stesso: List<out E>, Comparator<in T>. Senza nessuna delle due parole chiave, un parametro di tipo è invariante, come in MutableList<E>, che legge e scrive:
class Producer<out T>(private val item: T) {
fun get(): T = item // corretto: T è in uscita
// fun set(x: T) { } // errore: T è dichiarato out
}
interface Feeder<in T> {
fun feed(animal: T): String // corretto: T è in ingresso
// fun last(): T // errore: T è dichiarato in
}La varianza può anche essere scritta nel punto in cui viene usato un tipo. MutableList<in Int> accetta qualsiasi lista mutabile che possa contenere valori Int, come una MutableList<Number>, mentre MutableList<out Number> consente solo la lettura:
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() }All'interno di main:
val numbers = mutableListOf<Number>(0.5)
fillWith(numbers, 3)
println(numbers)
println(total(mutableListOf(1, 2, 3)))Output:
[0.5, 1, 2, 3]
6.0Sfida
FacileDue interfacce spostano gli elementi: Source<out T> produce elementi con next() (restituendo null quando è vuota) e Sink<in T> li consuma con accept(item). Sono fornite. Scrivi ListSource<T>(items), che restituisce gli elementi della lista uno alla volta e poi null; Collector<T>, che conserva gli elementi accettati in una lista items di sola lettura; e Counter, un Sink<Any> che conta tutto ciò che accetta. Poi scrivi pipe(from, vararg to), che sposta ogni elemento della sorgente in ogni sink e restituisce quanti elementi ha spostato.
Il codice fornito invia una riga di parole a un Collector<String> e una riga di numeri a un Collector<Number>, in entrambi i casi anche a un unico Counter. Funziona grazie a out e in: un ListSource<Int> è un Source<Number> e un Sink<Any> è un Sink<String>. Stampa i conteggi, entrambe le raccolte, counted: 7 e after the end: null.
Il tuo codice va inserito in ListSource.kt, Collector.kt, Counter.kt, Pipe.kt, Source.kt e Sink.kt. Main.kt contiene il codice di input/output fornito e non può essere modificato.
Provalo tu
fun main() {
// Codice di input/output fornito: lascialo così com'è
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()}")
}
Questa lezione include un breve quiz. Inizia la lezione per rispondere e tenere traccia dei tuoi progressi.
Tutte le lezioni di Programmazione orientata agli oggetti
1Classi e oggetti
Lavorare con i fileDefinire le classiCostruttori primariBlocchi init e convalidaCostruttori secondariRiepilogo - Tessera della biblioteca4Ereditarietà
Classi aperte e sottoclassiRidefinizione dei membrisuper e costruttoriClassi astratteRiepilogo - Elenco del personale2Proprietà e incapsulamento
Accessor personalizzatiModificatori di visibilitàSetter privatilazy e lateinitRipasso - Conto bancario5Interfacce
Dichiarare le interfacceMetodi predefinitiInterfacce multipleLe interfacce come tipiRiepilogo - Metodi di pagamento8Tipi generici
Funzioni genericheClassi genericheVincoli sui tipiVarianza con in e outRiepilogo - Coda con priorità11Progetto: Gestione della biblioteca
Libri e sociPrendere in prestito i libriEsercitati da solo: Playground Kotlin