Variance au site de déclaration : in et out
Utilisez correctement les paramètres de types covariants (out) et contravariants (in).
Variance au site de déclaration : in et out est une leçon Kotlin Academy gratuite sur CoddyKit. Ceci est la leçon 2 sur 4. Tu peux lire la leçon complète ci-dessous gratuitement — puis la pratiquer en direct dans le navigateur avec un éditeur de code intégré et un tuteur IA 24/7. Elle fait partie du parcours d'apprentissage Kotlin Academy, et ta progression se synchronise sur le web et l'application CoddyKit. Le cours Kotlin Academy comprend 4 leçons au total.
Le problème de la variance
Dans Kotlin, List<String> est un sous-type de List<Any>, car List est déclarée avec out T. Sans annotations de variance, cette relation n’existerait pas.
val strings: List<String> = listOf("a", "b")
val anys: List<Any> = strings // OK because List<out T>
// MutableList<String> is NOT a subtype of MutableList<Any>:
// val m: MutableList<Any> = mutableListOf("x") // compile error
fun main() { println(anys) }Covariance avec out
out T signifie que la classe peut uniquement produire des valeurs de type T (les retourner), et jamais les consommer. Ainsi, Producer<Dog> est un sous-type de Producer<Animal>.
interface Producer<out T> {
fun produce(): T
}
class DogProducer : Producer<String> {
override fun produce() = "Woof!"
}
fun sound(p: Producer<Any>) = println(p.produce())
fun main() {
val dog = DogProducer()
sound(dog) // OK: Producer<String> is a subtype of Producer<Any>
}Contravariance avec in
in T signifie que la classe peut uniquement consommer des valeurs de type T (les accepter comme paramètres), et jamais les produire. Ainsi, Consumer<Animal> est un sous-type de Consumer<Dog>.
interface Consumer<in T> {
fun consume(item: T)
}
class Printer : Consumer<Any> {
override fun consume(item: Any) = println(item)
}
fun feedDog(consumer: Consumer<String>) = consumer.consume("Dog")
fun main() {
val printer = Printer()
feedDog(printer) // OK: Consumer<Any> is a subtype of Consumer<String>
}Restriction de out : aucune position in
Avec out T, T ne peut pas apparaître en position de paramètre (in). Le compilateur l’impose.
interface ReadOnly<out T> {
fun get(): T // OK: out position
// fun set(t: T) {} // Error: T in in-position violates out variance
}
class Box<out T>(private val value: T) : ReadOnly<T> {
override fun get() = value
}
fun main() {
val box: ReadOnly<Any> = Box<String>("hello")
println(box.get())
}Restriction de in : aucune position out
Avec in T, T ne peut pas apparaître en position de retour (out).
interface WriteOnly<in T> {
fun set(t: T) // OK: in position
// fun get(): T {} // Error: T in out-position violates in variance
}
class Sink<in T> : WriteOnly<T> {
override fun set(t: T) = println("Received: $t")
}
fun main() {
val sink: WriteOnly<String> = Sink<Any>()
sink.set("hello")
}Exemples de la bibliothèque standard de Kotlin
Comparable<in T> est contravariant : un Comparable<Number> peut comparer n’importe quel sous-type numérique. List<out T> est covariant : une liste de chats est une liste d’animaux.
fun sortNumbers(list: List<Number>) = list.sortedWith(compareBy { it.toDouble() })
fun main() {
val ints: List<Int> = listOf(3, 1, 2)
val sorted = sortNumbers(ints) // OK: List<Int> is List<Number>
println(sorted)
}Invariance : MutableList
MutableList<T> est invariant : il peut à la fois produire et consommer T, donc aucune relation de sous-typage n’existe entre des types T différents.
fun addNumber(list: MutableList<Number>) { list.add(1.5) }
fun main() {
val ints = mutableListOf<Int>(1, 2)
// addNumber(ints) // compile error: MutableList<Int> is not MutableList<Number>
val nums = mutableListOf<Number>(1, 2)
addNumber(nums) // OK
println(nums)
}La variance au niveau de l’utilisation comme solution alternative
Lorsque vous ne pouvez pas modifier la classe, utilisez la variance au niveau de l’utilisation : out au niveau de l’appel pour projeter un type en type covariant.
fun copy(from: MutableList<out Any>, to: MutableList<Any>) {
for (item in from) to.add(item)
}
fun main() {
val src = mutableListOf("a", "b", "c")
val dest = mutableListOf<Any>()
copy(src, dest)
println(dest)
}Pratique : schéma Repository
Utilisez la covariance pour les référentiels en lecture seule et la contravariance pour les récepteurs en écriture seule afin de modéliser un flux de données clair.
interface Repository<out T> {
fun findAll(): List<T>
fun findById(id: Int): T?
}
interface Writer<in T> {
fun save(entity: T)
}
interface ReadWriteRepo<T> : Repository<T>, Writer<T>
// ReadWriteRepo<User> is neither sub nor super of ReadWriteRepo<Admin>Guide de décision sur la variance
Choisissez la variance selon l’utilisation de T : out = producteur (retourne uniquement T), in = consommateur (accepte uniquement T), invariant = les deux (MutableList, canal).
// Quick mental model:
// out T: source/producer — List, Flow, Sequence
// in T: sink/consumer — Comparable, Continuation
// invariant: read+write — MutableList, Channel, MutableStateFlow
fun main() {
val nums: List<Number> = listOf(1, 2, 3) // List is out
println(nums)
}Classe invariante avec une fonction out
Même dans une classe invariante, vous pouvez utiliser out au niveau de l’utilisation pour certains paramètres de fonction.
class Stack<T>(private val items: MutableList<T> = mutableListOf()) {
fun push(item: T) = items.add(item)
fun pop(): T? = items.removeLastOrNull()
}
fun printAll(stack: Stack<out Any>) {
// can only read, not write
println(stack.pop())
}
fun main() {
val s = Stack<String>()
s.push("hello")
printAll(s)
}Vérification rapide
Que signifie out T pour un paramètre de type ?
Récapitulatif
out T (covariance) autorise le sous-typage lors de la production de valeurs. in T (contravariance) autorise le sous-typage lors de la consommation de valeurs. Les types invariants (qui permettent à la fois la lecture et l’écriture) n’ont aucune relation de sous-typage entre des T différents.
Questions Fréquemment Posées
La leçon « Variance au site de déclaration : in et out » est-elle gratuite ?
Oui — le texte complet de « Variance au site de déclaration : in et out » est gratuit à lire ici sur le web. Pour la pratiquer de manière interactive (un éditeur de code intégré et un tuteur IA 24/7) et déverrouiller le reste du cours Kotlin Academy, passe à CoddyKit PRO. Le cours Kotlin Academy comprend 4 leçons au total.
Qu'est-ce que j'apprendrai dans « Variance au site de déclaration : in et out » ?
Utilisez correctement les paramètres de types covariants (out) et contravariants (in). Tu pratiques Kotlin Academy avec du code pratique que tu exécutes directement dans le navigateur, et un tuteur IA 24/7 répond à tes questions au fur et à mesure que tu avances dans la leçon.
Dois-je avoir de l'expérience pour commencer Kotlin Academy ?
Aucune expérience préalable n'est requise. Kotlin Academy sur CoddyKit est structuré pour les débutants jusqu'aux apprenants avancés, donc tu peux commencer ici ou depuis le début et avancer à ton rythme. Ceci est la leçon 2 sur 4.
Combien de temps prend la leçon « Variance au site de déclaration : in et out » ?
La plupart des leçons CoddyKit prennent environ 5–10 minutes. Chacune est courte et interactive, tu progresses régulièrement et tu repiques exactement où tu t'es arrêté sur le web et l'app.
Peux-tu écrire et exécuter du code dans cette leçon Kotlin Academy ?
Oui. Chaque leçon Kotlin Academy inclut un éditeur de code intégré, tu écris et exécutes du vrai code directement dans ton navigateur et tu reçois des retours IA instantanés — aucune configuration locale requise.
Toutes les leçons de ce cours
- Fonctions génériques et contraintes de types avec where
- Variance au site de déclaration : in et out
- Projection étoilée et utilisation de *
- Effacement des types et paramètres de types reified