Introduction : pourquoi la confusion entre l'astérisque et Any est normale
\n\nSi vous programmez en Kotlin depuis au moins quelques semaines, vous avez forcément rencontré les generics. Et si vous avez jeté un œil au code source de la bibliothèque standard ou écrit quelque chose comme List<*>, vous vous êtes peut-être demandé : « Pourquoi ne pas simplement écrire List<Any> ? L'astérisque ne signifie-t-elle pas "n'importe quel type" ? »
Cette question est un véritable classique sur Stack Overflow (des milliers de vues). Et ce n'est pas étonnant : à première vue, * et Any semblent interchangeables. Mais en réalité, il y a un fossé entre eux, et si vous ne le voyez pas, vous risquez d'écrire du code qui ne compile pas ou, pire encore, qui plante silencieusement à l'exécution.
Dans cet article, nous allons enfin clarifier la différence entre * (star projection) et Any dans les generics Kotlin. Vous apprendrez quand utiliser chacun, comment fonctionne la variance et pourquoi l'astérisque n'est pas un « Any », mais plutôt « je ne sais pas ce qu'il y a, mais je m'en fiche ». C'est parti !
Bref rappel sur les generics : pourquoi sont-ils nécessaires ?
\n\nLes generics (génériques) nous permettent d'écrire des classes et des fonctions qui fonctionnent avec différents types sans perdre la sécurité de typage. Au lieu d'écrire une classe séparée pour Int, une autre pour String et une autre pour User, nous écrivons une seule classe Box<T>.
Mais que faire si nous voulons écrire une fonction qui accepte n'importe quel Box, peu importe ce qu'il contient ? C'est là que nos héros entrent en scène : * et Any.
Any est un type concret (presque comme Object)
\n\nEn Kotlin, Any est la racine de la hiérarchie des types pour tous les types non null. Si vous écrivez List<Any>, cela signifie : « une liste qui contient des éléments de type Any ». Vous pouvez donc y mettre Int, String, User — tout ce que vous voulez, car tous héritent de Any.
Point important : List<Any> est un type paramétré concret. Le compilateur sait que le type de l'élément est Any.
val listOfAny: MutableList<Any> = mutableListOf(1, "bonjour", 3.14)\nlistOfAny.add(42) // OK\nlistOfAny.add("monde") // OK\n\n\nIci, tout est prévisible : nous avons explicitement dit que la liste fonctionne avec Any. Mais il y a un détail : MutableList<Any> n'est pas MutableList<String>. Vous ne pouvez pas passer une liste de chaînes là où une liste de Any est attendue (sauf si vous utilisez la covariance, mais nous y reviendrons).
L'astérisque (*) : « je ne sais pas, mais je m'en fiche »
\n\nL'astérisque, ou star projection, est une toute autre histoire. Quand vous écrivez List<*>, vous dites au compilateur : « C'est une liste d'un certain type, mais je ne sais pas lequel, et ça m'est égal ». C'est une projection de type inconnu.
La différence clé : * n'est pas un type, mais un wildcard (joker). Il signifie que le type est inconnu, et le compilateur impose des restrictions sur ce que vous pouvez faire avec un tel objet.
fun printLength(list: List<*>) {\n // list.add(1) // Erreur de compilation ! Impossible d'ajouter un élément\n println(list.size) // OK : on peut connaître la taille\n for (item in list) {\n println(item) // OK : item est de type Any?\n }\n}\n\n\nRemarquez : lorsque vous lisez des éléments d'une List<*>, ils sont de type Any? (car la liste peut contenir null). Mais vous ne pouvez pas ajouter d'éléments, car le compilateur ne sait pas quel type est réellement stocké. C'est une protection contre la violation de la sécurité de typage.
La principale différence en deux mots
\n\n- \n
List<Any>est une liste qui peut contenir n'importe quel type (car tous héritent deAny). Vous pouvez lire et écrire des éléments. \n List<*>est une liste d'un type inconnu. Vous pouvez lire des éléments (qui seront de typeAny?), mais vous ne pouvez pas en écrire, car le compilateur ne connaît pas le type exact. \n
En résumé : Any est un type réel, tandis que * est un marqueur d'incertitude. Choisissez Any quand vous voulez manipuler des objets de n'importe quel type, et * quand vous voulez travailler avec un type inconnu sans le modifier.