
Listes (`List`), Ensembles (`Set`), Dictionnaires (`Map`)
Découvrez les collections fondamentales en Kotlin : List (ordonnée, doublons permis), Set (unique, non ordonné) et Map (paires clé-valeur). Apprenez à les créer et comprendre leurs usages.
Introduction : organiser des ensembles de données
Au-delà des types de base simples comme `Int` ou `String`, les programmes ont très souvent besoin de manipuler des groupes ou des ensembles d'éléments. Pensez à une liste de tâches à faire, un ensemble d'utilisateurs uniques connectés, ou un dictionnaire associant des mots à leurs définitions. Pour gérer ces scénarios, Kotlin, comme la plupart des langages modernes, fournit des structures de données appelées collections.
Les collections sont des objets conçus pour stocker et organiser plusieurs éléments (qui peuvent être du même type ou de types différents, bien que les collections typées soient préférables pour la sécurité). La bibliothèque standard de Kotlin offre une hiérarchie riche de collections, mais nous allons nous concentrer ici sur les trois interfaces les plus fondamentales et universellement utilisées : `List`, `Set`, et `Map`.
Une distinction clé dans l'écosystème des collections Kotlin est celle entre les collections immutables (lecture seule, dont l'état ne peut pas changer après création) et les collections mutables (qui peuvent être modifiées après création en ajoutant, supprimant ou modifiant des éléments). Dans cette section, nous nous concentrons sur les interfaces de base (`List`, `Set`, `Map`), qui représentent par défaut la vue immutable. Les fonctions standards comme `listOf()`, `setOf()`, `mapOf()` créent des instances immutables.
`List` : la collection ordonnée
Une `List` est une collection ordonnée d'éléments. Cela signifie que les éléments sont stockés dans un ordre spécifique et conservent cet ordre. Vous pouvez accéder à un élément d'une liste par sa position (son index), qui commence à 0 pour le premier élément.
Les listes autorisent les doublons : un même élément peut apparaître plusieurs fois à différentes positions dans la liste.
Pensez à une liste de courses, aux étapes d'une recette, ou à une playlist musicale : l'ordre est important et des éléments identiques peuvent coexister.
Pour créer une liste immutable, on utilise généralement la fonction `listOf()`:
fun main() {
// Création de listes immutables
val names: List = listOf("Alice", "Bob", "Charlie", "Alice")
val numbers = listOf(1, 10, 5, 8, 10) // Inférence de type: List
val emptyList: List = listOf() // Liste vide
println("Noms: $names") // Affiche: Noms: [Alice, Bob, Charlie, Alice]
println("Nombres: $numbers") // Affiche: Nombres: [1, 10, 5, 8, 10]
// Accès par index (commence à 0)
val firstName = names[0] // ou names.get(0)
val thirdNumber = numbers[2]
println("Premier nom: $firstName") // Affiche: Premier nom: Alice
println("Troisième nombre: $thirdNumber") // Affiche: Troisième nombre: 5
// Taille de la liste
println("Nombre de noms: ${names.size}") // Affiche: Nombre de noms: 4
// Vérifier si un élément est présent
val containsBob = names.contains("Bob")
println("La liste contient Bob: $containsBob") // Affiche: La liste contient Bob: true
// names.add("David") // Erreur de compilation: add n'existe pas sur List (immutable)
} `List` est probablement le type de collection le plus utilisé lorsque l'ordre des éléments est significatif ou lorsque les doublons sont autorisés.
`Set` : la collection d'éléments uniques
Un `Set` est une collection qui ne contient que des éléments uniques. Si vous essayez d'ajouter un élément qui est déjà présent dans l'ensemble, l'opération est simplement ignorée (dans le cas d'un `MutableSet`).
Contrairement à `List`, un `Set` ne garantit généralement pas l'ordre des éléments. Bien que certaines implémentations spécifiques comme `LinkedHashSet` puissent maintenir l'ordre d'insertion, l'interface `Set` elle-même ne le promet pas. Vous ne pouvez donc pas accéder aux éléments par un index numérique.
Pensez à un groupe de participants uniques à un événement, à l'ensemble des mots distincts dans un texte, ou aux différentes couleurs disponibles pour un produit.
Pour créer un ensemble immutable, on utilise `setOf()`:
fun main() {
// Création d'ensembles immutables
val uniqueColors: Set = setOf("Rouge", "Vert", "Bleu", "Rouge", "Vert")
val primeNumbers = setOf(2, 3, 5, 7, 11, 3, 5)
val emptySet: Set = setOf()
// Les doublons sont automatiquement ignorés à la création
println("Couleurs uniques: $uniqueColors") // Affiche: Couleurs uniques: [Rouge, Vert, Bleu] (l'ordre peut varier)
println("Nombres premiers: $primeNumbers") // Affiche: Nombres premiers: [2, 3, 5, 7, 11] (l'ordre peut varier)
// Taille de l'ensemble
println("Nombre de couleurs: ${uniqueColors.size}") // Affiche: Nombre de couleurs: 3
// Vérifier si un élément est présent (opération très efficace sur les Set)
val hasGreen = uniqueColors.contains("Vert")
val hasYellow = uniqueColors.contains("Jaune")
println("L'ensemble contient Vert: $hasGreen") // Affiche: L'ensemble contient Vert: true
println("L'ensemble contient Jaune: $hasYellow") // Affiche: L'ensemble contient Jaune: false
// Pas d'accès par index
// val firstColor = uniqueColors[0] // Erreur de compilation: Pas d'opérateur get avec Int
} `Set` est idéal lorsque vous avez besoin de garantir l'unicité des éléments et que l'ordre n'est pas important, ou lorsque vous effectuez fréquemment des recherches de présence (l'opération `contains` est généralement plus rapide sur un `Set` que sur une `List`).
`Map` (Dictionnaire) : la collection de paires clé-valeur
Une `Map` (souvent appelée dictionnaire ou tableau associatif dans d'autres contextes) est une collection qui stocke des paires clé-valeur. Chaque élément dans une Map est une entrée composée d'une clé unique et de la valeur qui lui est associée.
Les clés dans une `Map` doivent être uniques. Si vous tentez d'insérer une paire avec une clé qui existe déjà, l'ancienne valeur associée à cette clé sera remplacée (dans le cas d'une `MutableMap`). Les valeurs, en revanche, peuvent être des doublons.
Les `Map` permettent de retrouver très efficacement une valeur en utilisant sa clé correspondante.
Pensez à un dictionnaire (clé = mot, valeur = définition), un annuaire téléphonique (clé = nom, valeur = numéro), ou aux en-têtes d'une requête HTTP (clé = nom de l'en-tête, valeur = contenu de l'en-tête).
Pour créer une map immutable, on utilise `mapOf()` et l'opérateur infixe `to` pour créer les paires (`Pair`) :
fun main() {
// Création de maps immutables
val userAges: Map = mapOf("Alice" to 30, "Bob" to 25, "Charlie" to 30)
val httpStatusCodes = mapOf(
200 to "OK",
404 to "Not Found",
500 to "Internal Server Error"
) // Inférence: Map
val emptyMap: Map = mapOf()
println("Ages: $userAges") // Affiche: Ages: {Alice=30, Bob=25, Charlie=30}
println("Codes HTTP: $httpStatusCodes") // Affiche: Codes HTTP: {200=OK, 404=Not Found, 500=Internal Server Error}
// Accès à une valeur par sa clé
val aliceAge = userAges["Alice"] // Syntaxe d'indexation
val status404 = httpStatusCodes[404] // ou .get(404)
val unknownUserAge = userAges["David"] // Clé inexistante
println("Age d'Alice: $aliceAge") // Affiche: Age d'Alice: 30
println("Statut 404: $status404") // Affiche: Statut 404: Not Found
println("Age de David: $unknownUserAge") // Affiche: Age de David: null (retourne null si la clé n'existe pas)
// Taille de la map (nombre de paires)
println("Nombre d'utilisateurs: ${userAges.size}") // Affiche: Nombre d'utilisateurs: 3
// Vérifier la présence d'une clé ou d'une valeur
val hasBob = userAges.containsKey("Bob")
val hasAge30 = userAges.containsValue(30)
println("Contient la clé 'Bob': $hasBob") // Affiche: Contient la clé 'Bob': true
println("Contient la valeur 30: $hasAge30") // Affiche: Contient la valeur 30: true
// Obtenir les collections de clés ou de valeurs
val names: Set = userAges.keys
val ages: Collection = userAges.values
println("Toutes les clés (noms): $names") // Affiche: Toutes les clés (noms): [Alice, Bob, Charlie]
println("Toutes les valeurs (âges): $ages") // Affiche: Toutes les valeurs (âges): [30, 25, 30]
} `Map` est la structure de choix lorsque vous avez besoin d'associer des informations et de les retrouver rapidement via un identifiant unique (la clé).
Focus sur l'immutabilité par défaut
Il est crucial de se rappeler que `listOf()`, `setOf()`, et `mapOf()` créent des collections immutables. Une fois créées, vous ne pouvez pas ajouter, supprimer ou modifier leurs éléments. Toute tentative de le faire résultera en une erreur de compilation (si la méthode n'existe pas sur l'interface immutable) ou une exception à l'exécution (pour certaines opérations sur des vues immutables de collections mutables, un cas plus avancé).
Cette immutabilité par défaut est une bonne pratique encouragée par Kotlin car elle conduit à un code plus sûr, notamment dans des environnements multithreadés, et rend le raisonnement sur l'état du programme plus simple. Si vous avez besoin de modifier une collection après sa création, vous utiliserez les versions mutables (`mutableListOf()`, `mutableSetOf()`, `mutableMapOf()`) que nous aborderons séparément.
Récapitulatif : choisir la bonne collection
Le choix entre `List`, `Set`, et `Map` dépend de vos besoins spécifiques :
- Utilisez `List` lorsque l'ordre des éléments est important et que les doublons sont autorisés. Accès par index.
- Utilisez `Set` lorsque vous avez besoin de garantir l'unicité des éléments et que l'ordre n'est pas une préoccupation majeure. Idéal pour les vérifications de présence rapides.
- Utilisez `Map` lorsque vous avez besoin de stocker des associations clé-valeur et de retrouver rapidement une valeur grâce à sa clé unique.
Ces trois types de collections forment la base de la manipulation de groupes de données en Kotlin et sont enrichis par une vaste bibliothèque de fonctions d'extension pour effectuer des opérations courantes comme le filtrage, la transformation, et l'itération, que nous explorerons bientôt.