Generics en Kotlin : pourquoi sont-ils nécessaires 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 (génériques) sont l'une des fonctionnalités clés du langage Kotlin, permettant d'écrire un code flexible, type-safe et réutilisable. Si vous avez déjà écrit List<String> ou Map<Int, User>, vous avez déjà utilisé des génériques sans même y penser. Mais que se cache-t-il derrière ces chevrons et comment en tirer le maximum ? Dans cet article, nous allons voir pourquoi les Generics sont nécessaires en Kotlin, comment les appliquer en pratique et quelles fonctionnalités avancées (variance, reified, where) vous aideront à écrire un code encore plus propre et sûr.



Cet article est destiné aux développeurs qui connaissent déjà les bases de Kotlin et souhaitent approfondir leurs connaissances. Nous aborderons tout : de la syntaxe de base aux exemples concrets de code de production.



Pourquoi les Generics sont-ils nécessaires ? Le problème sans eux



Imaginez que vous devez écrire une fonction qui prend une liste d'éléments quelconques et retourne le premier élément. Sans génériques, vous écririez ceci :



fun getFirst(list: List<Any>): Any {    return list[0]}


Maintenant, si vous passez un List<String>, vous obtenez un Any en sortie. Pour travailler avec comme une chaîne, vous devez faire un cast explicite :



val first = getFirst(listOf("Kotlin", "Java")) as Stringfirst.length // Dangereux ! Peut lever une ClassCastException


C'est peu pratique et dangereux. L'erreur n'apparaîtra qu'à l'exécution. Les Generics résolvent ce problème en préservant l'information de type au moment de la compilation.



Principaux avantages des Generics :

  • Type-safety — le compilateur vérifie les types à la compilation, pas à l'exécution.
  • Élimination des casts explicites — le code devient plus propre et plus lisible.
  • Réutilisabilité — une même classe ou fonction fonctionne avec différents types.


Syntaxe de base : classes, fonctions et contraintes



Classes génériques



La façon la plus simple de commencer est de déclarer une classe avec un ou plusieurs paramètres de type. Les paramètres de type sont indiqués entre chevrons <T> (la lettre peut être quelconque, mais on utilise traditionnellement T, E, K, V) :



class Box<T>(val value: T)

fun main() { val stringBox = Box("Kotlin") // Le type est inféré automatiquement val intBox = Box(42) // Box<Int>

println(stringBox.value.length) // Fonctionne sans cast ! println(intBox.value + 1) // 43}


Kotlin sait inférer le type, donc il n'est pas nécessaire d'écrire Box<String> — Box("Kotlin") suffit.



Fonctions génériques



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



fun <T> getMiddleElement(list: List<T>): T? {    if (list.isEmpty()) return null    return list[list.size / 2]}

fun main() { val numbers = listOf(1, 2, 3, 4, 5) val middle = getMiddleElement(numbers) // Le type Int? est inféré automatiquement println(middle) // 3}


Contraintes (where / upper bound)



Parfois, il est nécessaire de limiter les types pouvant être utilisés dans un générique. Par exemple, pour appeler la méthode .compareTo(), le type doit implémenter l'interface Comparable :



fun <T : Comparable<T>> maxOfTwo(a: T, b: T): T {    return if (a >= b) a else b}

fun main() { println(maxOfTwo(10, 20)) // 20 println(maxOfTwo("A", "B")) // B // maxOfTwo(10, "A") // Erreur de compilation : types incompatibles}


Si plusieurs contraintes sont nécessaires, utilisez le mot-clé where :



interface Printable {    fun print()}

fun <T> process(item: T) where T : Comparable<T>, T : Printable

Blogs

Book Recommendations