Generics en Kotlin : pourquoi et comment les utiliser

Formateur Python en ligne pour débutants

Apprenez Python facilement sans surcharge théorique. Résolvez des tâches pratiques avec vérification automatique et écrivez du code directement dans le navigateur.

Start Course

Introduction aux Generics en Kotlin



Les Generics (types génériques) sont l'une des fonctionnalités clés des langages de programmation typés statiquement, y compris Kotlin. Ils permettent d'écrire un code flexible et réutilisable qui fonctionne avec différents types de données, tout en maintenant une sécurité de type stricte au moment de la compilation.



Imaginez que vous deviez créer une structure de données pour stocker une liste d'éléments. Sans generics, vous devriez écrire des classes séparées pour chaque type (IntList, StringList, UserList). Avec les generics, une seule classe MyList<T> suffit, qui fonctionnera avec n'importe quel type.



// Sans Generics — duplication de code nécessaireclass IntList(private val items: MutableList<Int>)class StringList(private val items: MutableList<String>)

// Avec Generics — une seule classe pour tous les typesclass GenericList<T>(private val items: MutableList<T>) { fun add(item: T) { items.add(item) } fun get(index: Int): T = items[index]}


Dans cet article, nous allons détailler le fonctionnement des generics en Kotlin, examiner leur syntaxe, leurs cas d'utilisation et les concepts avancés comme la covariance et la contravariance.



Fondamentaux des Generics



Déclaration des classes et fonctions génériques



Pour déclarer une classe générique, il suffit d'indiquer un ou plusieurs paramètres de type entre chevrons après le nom de la classe :



class Box<T>(var value: T) {    fun getValue(): T = value    fun setValue(newValue: T) { value = newValue }}

// Utilisationval intBox = Box<Int>(42)val stringBox = Box<String>("Bonjour")

// Kotlin peut inférer le type, on peut donc écrire plus courtval anotherBox = Box("Inférence automatique de type")


Les fonctions peuvent aussi être génériques. Le paramètre de type est indiqué avant le nom de la fonction :



fun <T> printItem(item: T) {    println("Élément : $item")}

fun <T> createPair(first: T, second: T): Pair<T, T> { return Pair(first, second)}

// UtilisationprintItem(123)printItem("Texte")val pair = createPair("A", "B")


Bornes de type (Type Bounds)



Il est parfois nécessaire de limiter les types pouvant être utilisés dans les generics. Pour cela, on utilise des bornes supérieures (upper bounds) avec le mot-clé : Type :



// Uniquement les types implémentant Comparablefun <T : Comparable<T>> findMax(a: T, b: T): T {    return if (a > b) a else b}

// Plusieurs contraintes avec wherefun <T> processItem(item: T) where T : CharSequence, T : Comparable<T> { println("Longueur : ${item.length}") println("Comparé : ${item.compareTo(item)}")}

// Utilisationprintln(findMax(10, 20)) // 20println(findMax("Pomme", "Banane")) // "Banane"// findMax(10, "Texte") // Erreur de compilation !


Variance : in, out et projection de types



L'un des sujets les plus complexes mais importants des generics est la variance. Elle définit comment les types génériques se rapportent les uns aux autres lors de l'héritage.



Invariance (par défaut)



Par défaut, les generics en Kotlin sont invariants. Cela signifie que Box<Number> et Box<Int> ne sont pas des sous-types l'un de l'autre, même si Int est un sous-type de Number :



val intBox: Box<Int> = Box(42)// val numBox: Box<Number> = intBox // Erreur de compilation !


Covariance (out)



Le modificateur out rend le type covariant. Cela signifie que Box<Int> devient un sous-type de Box<Number>. Il est utilisé lorsque le type T est utilisé uniquement en position de sortie (valeurs de retour) :



class Producer<out T>(private val item: T) {    fun produce(): T = item  // T uniquement en sortie}                

Blogs

Book Recommendations