Variance avec in et out
Fait partie de la section Programmation orientée objet du Journey Kotlin de Coddy. Leçon 40 sur 57.
Un List<String> peut être transmis là où un List<Any> est attendu, car un List ne fait que fournir des éléments. Un MutableList<String> ne le peut pas : le code qui le considérerait comme un MutableList<Any> pourrait ajouter un Int à une liste de chaînes :
fun describeAll(items: List<Any>) = items.joinToString { it::class.simpleName ?: "?" }
fun addNumber(items: MutableList<Any>) { items.add(42) }Dans main :
val words = listOf("a", "b")
println(describeAll(words))
val mixed = mutableListOf<Any>("x")
addNumber(mixed)
println(mixed)
// addNumber(mutableListOf("a")) // erreur : MutableList<String> n'est pas un MutableList<Any>Sortie :
String, String
[x, 42]out T rend une classe générique covariante : elle produit uniquement des valeurs de type T, en tant que types de retour, et n'en reçoit jamais. Ainsi, un Producer<Cat> est considéré comme 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}"Dans main :
val cats: Producer<Cat> = Producer(Cat("Tom"))
println(greet(cats))Sortie :
hello Tomin T la rend contravariante : elle ne fait que consommer des valeurs de T, en tant que paramètres. Un consommateur capable de gérer n’importe quel Animal peut remplacer un consommateur de 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) }À l’intérieur de main :
println(feedCats(listOf(Cat("Tom"), Cat("Kit")), AnimalFeeder()))Sortie :
[fed Tom, fed Kit]Le compilateur impose la direction : un paramètre de type out ne peut pas être le type d’un paramètre de méthode, et un paramètre in ne peut pas être renvoyé. La règle générale est producteur out, consommateur in. La bibliothèque standard fait de même : List<out E>, Comparator<in T>. Sans aucun de ces mots-clés, un paramètre de type est invariant, comme dans MutableList<E>, qui permet à la fois de lire et d’écrire :
class Producer<out T>(private val item: T) {
fun get(): T = item // OK : T sort
// fun set(x: T) { } // erreur : T est déclaré out
}
interface Feeder<in T> {
fun feed(animal: T): String // OK : T entre
// fun last(): T // erreur : T est déclaré in
}La variance peut également s’écrire là où un type est utilisé. MutableList<in Int> accepte toute liste mutable pouvant recevoir des valeurs Int, comme une MutableList<Number>, tandis que MutableList<out Number> autorise uniquement la lecture :
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() }Dans main :
val numbers = mutableListOf<Number>(0.5)
fillWith(numbers, 3)
println(numbers)
println(total(mutableListOf(1, 2, 3)))Sortie :
[0.5, 1, 2, 3]
6.0Défi
FacileDeux interfaces déplacent des éléments : Source<out T> produit des éléments avec next() (en renvoyant null lorsqu'il est vide) et Sink<in T> les consomme avec accept(item). Elles sont fournies. Écrivez ListSource<T>(items), qui renvoie les éléments de la liste un par un, puis null ; Collector<T>, qui conserve les éléments acceptés dans une liste items en lecture seule ; et Counter, un Sink<Any> qui compte tout ce qu'il accepte. Écrivez ensuite pipe(from, vararg to), qui déplace chaque élément de la source vers chaque puits et renvoie le nombre d'éléments déplacés.
Le code fourni achemine une ligne de mots vers un Collector<String> et une ligne de nombres vers un Collector<Number>, dans les deux cas également vers un même Counter. Cela fonctionne grâce à out et in : un ListSource<Int> est un Source<Number>, et un Sink<Any> est un Sink<String>. Il affiche les nombres, les deux collections, counted: 7 et after the end: null.
Votre code doit être placé dans ListSource.kt, Collector.kt, Counter.kt, Pipe.kt, Source.kt et Sink.kt. Main.kt contient le code d'entrée/sortie fourni et ne peut pas être modifié.
Essayez vous-même
fun main() {
// Code d'entrée/sortie fourni : le garder tel quel
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()}")
}
Cette leçon comprend un petit quiz. Commencez la leçon pour y répondre et suivre votre progression.
Toutes les leçons de Programmation orientée objet
1Classes et objets
Travailler avec des fichiersDéfinir des classesConstructeurs primairesBlocs init et validationConstructeurs secondairesRécapitulatif - Carte de bibliothèque4Héritage
Classes ouvertes et sous-classesRedéfinition des membressuper et constructeursClasses abstraitesRécapitulatif – Annuaire du personnel2Propriétés et encapsulation
Accesseurs personnalisésModificateurs de visibilitéSetters privéslazy et lateinitRécapitulatif - Compte bancaire5Interfaces
Déclarer des interfacesMéthodes par défautInterfaces multiplesInterfaces en tant que typesRécapitulatif - Méthodes de paiement8Génériques
Fonctions génériquesClasses génériquesContraintes de typeVariance avec in et outRécapitulatif - File de priorité11Projet : Gestion de bibliothèque
Livres et membresEmprunter des livresEntraînez-vous par vous-même : Playground Kotlin