Que se cache-t-il derrière le mot out en Kotlin ? Réponses aux questions fréquentes
\n\nSi vous avez déjà écrit des génériques en Kotlin et rencontré une erreur de compilation incompréhensible, vous avez probablement croisé le mot out. Ce petit modificateur suscite de nombreux débats même parmi les développeurs expérimentés. Nous avons rassemblé les questions les plus fréquentes de Stack Overflow et les avons transformées en un guide clair. Aujourd'hui, vous apprendrez ce qu'est out, à quoi il sert et comment ne pas se perdre dans la covariance.
Démêlons cela une fois pour toutes — sans théorie ennuyeuse, uniquement du code vivant et de la pratique.
\n\nPourquoi Kotlin a-t-il inventé out ? (Bref retour historique)
\n\nEn Java, il existe le wildcard — le point d'interrogation ? extends T. Il signifie qu'une collection peut contenir des objets de type T ou de ses sous-types. Le problème est que Java ne vous interdit pas de placer accidentellement un objet incorrect dans une telle collection — vous n'obtiendrez une erreur qu'à l'exécution. Kotlin a résolu ce problème au niveau des types.
Le mot-clé out en Kotlin est une déclaration de covariance. Si vous écrivez Source<out T>, cela signifie : « Je peux lire des objets de type T depuis ce Source, mais je ne peux pas y écrire ». Le compilateur veille strictement à cela.
Question 1 : Quelle est la différence entre out et in ?
\n\nLa question la plus fréquente sur Stack Overflow. Imaginons une boîte :
\n\n- \n
out— une boîte dans laquelle on ne peut que prendre des objets (elle produit). \n in— une boîte dans laquelle on ne peut que mettre des objets (elle consomme). \n
Exemple :
\n\n// Covariance : on peut lire, mais pas écrire\ninterface Producer<out T> {\n fun produce(): T\n}\n\n// Contravariance : on peut écrire, mais pas lire\ninterface Consumer<in T> {\n fun consume(item: T)\n}\n\nfun main() {\n val stringProducer: Producer<String> = object : Producer<String> {\n override fun produce(): String = "Hello"\n }\n \n val anyProducer: Producer<Any> = stringProducer // Fonctionne ! out le permet\n println(anyProducer.produce())\n}\n\n\nRemarquez : Producer<out T> permet d'assigner Producer<String> à une variable de type Producer<Any>. Sans out, cela serait impossible.
Question 2 : Pourquoi Kotlin interdit-il fun set(value: T) avec out ?
\n\nC'est un piège classique. Si vous déclarez out, le compilateur ne vous autorisera pas à avoir des fonctions qui prennent T en paramètre. Pourquoi ?
Imaginez que vous ayez un MutableList<out Number>. Vous pourriez y mettre un Int, mais le compilateur ne sait pas quel est réellement ce type de liste. Peut-être s'agit-il d'un MutableList<Double> ? Si l'écriture était autorisée, vous casseriez les types. out garantit la sécurité : vous ne pouvez que lire.
// Ne se compilera pas !\ninterface BadList<out T> {\n fun get(index: Int): T // OK\n fun add(item: T) // Erreur ! T est en position in\n}\n\n\nSolution : si vous avez besoin à la fois de lire et d'écrire, utilisez in et out séparément, ou un type invariant (sans modificateur).
\n\nQuestion 3 : Comment out est-il lié au wildcard ? extends de Java ?
\n\nCorrespondance directe : Source<out T> en Kotlin = Source<? extends T> en Java. Mais il y a une différence importante :
- \n
- En Java, le wildcard est une variance d'utilisation (use-site variance). \n
- En Kotlin,
outest une variance de déclaration (declaration-site variance). \n
Exemple en Java :
\n\n// Java : utilisation du wildcard lors de la déclaration de variable\nList<? extends Number> numbers = new ArrayList<Integer>();\nnumbers.a