Contactez-nous

Le `when` : une alternative puissante au `switch`

Découvrez la structure de contrôle `when` en Kotlin. Une alternative flexible et puissante au `switch` traditionnel pour gérer multiples conditions, types, et plages.

Introduction : au-delà du `switch` traditionnel

Là où de nombreux langages de programmation (comme Java, C++, C# avant certaines évolutions) proposent une instruction `switch` pour gérer une sélection parmi plusieurs cas basés sur une valeur unique (souvent limitée à des types simples comme les entiers ou les énumérations), Kotlin offre une alternative beaucoup plus flexible et puissante : l'expression `when`.

`when` peut être vu comme une généralisation et une amélioration du `switch`. Il permet non seulement de comparer une valeur à plusieurs constantes, mais aussi de vérifier si elle appartient à une plage ou une collection, de tester son type, ou même d'évaluer des conditions booléennes arbitraires. De plus, tout comme `if`/`else`, `when` peut être utilisé à la fois comme une instruction (pour exécuter du code) et comme une expression (pour retourner une valeur), ce qui le rend extrêmement polyvalent.

Comprendre et maîtriser `when` est essentiel pour écrire du code Kotlin idiomatique, concis et expressif lorsqu'il s'agit de gérer des embranchements conditionnels multiples.

Syntaxe de base : remplacer `if-else if` et `switch`

La forme la plus simple de `when` prend un argument (la valeur à tester) entre parenthèses. Chaque cas possible est défini sur une ligne séparée, avec la valeur (ou la condition) à gauche, suivie d'une flèche `->`, puis du bloc de code ou de l'expression à exécuter/retourner si ce cas correspond.

Syntaxe avec argument :

when (argument) {
    valeur1 -> // Code si argument == valeur1
    valeur2 -> // Code si argument == valeur2
    // ... autres cas ...
    else -> // Code si aucun des cas précédents ne correspond (optionnel comme instruction, souvent requis comme expression)
}

Dès qu'une correspondance est trouvée, le code associé est exécuté et l'exécution du `when` se termine (il n'y a pas de "fall-through" implicite comme dans certains `switch` où un `break` est nécessaire). La branche `else` capture tous les cas non explicitement listés.

fun describeNumber(x: Int) {
    when (x) {
        1 -> println("C'est un.")
        2 -> println("C'est deux.")
        3, 4 -> println("C'est trois ou quatre.") // Plusieurs valeurs pour une branche
        in 5..10 -> println("C'est entre cinq et dix (inclus).") // Utilisation d'une plage (range)
        !in 11..20 -> println("Ce n'est pas entre onze et vingt.") // Négation d'une plage
        else -> {
            println("C'est un autre nombre: $x") // Bloc de code pour l'else
            println("Traitement par défaut.")
        }
    }
}

fun main() {
    describeNumber(2)
    describeNumber(4)
    describeNumber(7)
    describeNumber(25)
    describeNumber(15)
}

Ce seul exemple montre déjà la flexibilité de `when` : il gère des valeurs spécifiques, des combinaisons de valeurs (avec la virgule), et même des plages (`in`).

Conditions avancées : plages, collections et types

La puissance de `when` réside dans la variété des conditions que vous pouvez utiliser dans les branches :

  • Plusieurs valeurs : Séparez les valeurs par une virgule (`,`) pour exécuter le même code pour plusieurs correspondances exactes (`3, 4 -> ...`).
  • Plages (`in` / `!in`) : Utilisez l'opérateur `in` (ou `!in` pour la négation) suivi d'une plage (`start..end`) pour vérifier si la valeur se trouve dans cet intervalle (`in 5..10 -> ...`).
  • Collections (`in` / `!in`) : Utilisez `in` (ou `!in`) avec une collection (comme une `List` ou un `Set`) pour vérifier l'appartenance (`in listOf("admin", "moderator") -> ...`).
  • Vérification de type (`is` / `!is`) : Utilisez l'opérateur `is` (ou `!is`) pour vérifier si l'argument est d'un certain type. Un avantage majeur ici est que Kotlin effectue un smart cast : si la condition `is Type` est vraie, l'argument est automatiquement traité comme étant de ce `Type` dans la branche correspondante, sans besoin de cast explicite.
fun checkType(obj: Any) { // Any est le supertype de tous les types non-nullables
    when (obj) {
        is String -> {
            println("C'est une chaîne de caractères.")
            println("Sa longueur est ${obj.length}") // Smart cast: obj est traité comme String
        }
        is Int -> {
            println("C'est un entier.")
            println("Son double est ${obj * 2}") // Smart cast: obj est traité comme Int
        }
        is List<*> -> { // Vérifie si c'est une liste (peu importe le type des éléments)
            println("C'est une liste.")
            println("Elle contient ${obj.size} éléments.") // Smart cast: obj est traité comme List
        }
        !is Boolean -> println("Ce n'est pas un booléen.")
        else -> println("Type inconnu ou booléen.")
    }
}

fun main() {
    checkType("Hello Kotlin")
    checkType(42)
    checkType(listOf(1, 2, 3))
    checkType(true)
    checkType(3.14) // Matchera !is Boolean
}

`when` sans argument : une chaîne `if-else if` améliorée

Vous pouvez également utiliser `when` sans lui passer d'argument entre parenthèses. Dans ce cas, les conditions dans les branches doivent être des expressions booléennes complètes. `when` évalue chaque branche séquentiellement et exécute le code de la première branche dont la condition est `true`.

Cela le rend très similaire à une chaîne `if-else if-else`, mais souvent avec une syntaxe plus alignée et potentiellement plus lisible.

Syntaxe sans argument :

when {
    condition1 -> // Code si condition1 est true
    condition2 -> // Code si condition1 est false et condition2 est true
    // ... autres conditions ...
    else -> // Code si toutes les conditions précédentes sont false
}
fun getPermissionLevel(userName: String, userRole: String): String {
    return when { // when utilisé comme expression ici
        userName == "admin" -> "Super Administrateur"
        userRole == "moderator" && userName.startsWith("mod_") -> "Modérateur Confirmé"
        userRole == "editor" || userRole == "writer" -> "Contributeur"
        userName.isEmpty() -> "Invité (Nom vide)"
        else -> "Utilisateur Standard"
    }
}

fun main() {
    println(getPermissionLevel("admin", "user")) // Super Administrateur
    println(getPermissionLevel("mod_alice", "moderator")) // Modérateur Confirmé
    println(getPermissionLevel("bob", "writer")) // Contributeur
    println(getPermissionLevel("", "user")) // Invité (Nom vide)
    println(getPermissionLevel("charlie", "viewer")) // Utilisateur Standard
}

`when` utilisé comme une Expression

Tout comme `if`, `when` est plus puissant en Kotlin car il peut être utilisé directement comme une expression. La valeur de l'expression `when` est la valeur de la dernière expression du bloc de la branche qui a été exécutée.

Lorsque `when` est utilisé comme expression (c'est-à-dire que son résultat est assigné à une variable, retourné par une fonction, etc.), le compilateur exige généralement que le `when` soit exhaustif. Cela signifie que toutes les possibilités pour l'argument doivent être couvertes par les branches. Le moyen le plus simple de garantir l'exhaustivité est de fournir une branche `else`.

fun getNumberName(x: Int): String {
    val name = when (x) {
        1 -> "Un"
        2 -> "Deux"
        3 -> "Trois"
        else -> "Autre"
    }
    return name
}

// Version plus concise (retourne directement le résultat de when)
fun getNumberNameConcise(x: Int): String {
    return when (x) {
        1 -> "Un"
        2 -> "Deux"
        3 -> "Trois"
        else -> "Autre" // 'else' est obligatoire ici car x peut être autre chose que 1, 2, 3
    }
}

// Exemple avec type checking
fun getTypeCode(obj: Any): Int {
    return when (obj) {
        is String -> 1
        is Int -> 2
        is List<*> -> 3
        else -> 0 // 'else' est obligatoire car obj peut être d'un autre type
    }
}

fun main() {
    println(getNumberNameConcise(2)) // Affiche: Deux
    println(getNumberNameConcise(5)) // Affiche: Autre
    println(getTypeCode("test")) // Affiche: 1
    println(getTypeCode(true))   // Affiche: 0
}

L'utilisation de `when` comme expression conduit souvent à un code plus fonctionnel et plus lisible, en particulier pour déterminer une valeur basée sur plusieurs conditions.

Exhaustivité et cas particuliers (Enums, Sealed Classes)

Il y a des cas où la branche `else` n'est pas strictement nécessaire pour un `when` utilisé comme expression. C'est lorsque le compilateur peut vérifier statiquement que toutes les possibilités ont été couvertes. Les exemples les plus courants sont lorsque l'argument du `when` est une énumération (`enum`) ou une classe scellée (`sealed class`).

Si vous listez explicitement toutes les constantes de l'énumération ou tous les sous-types de la classe scellée dans les branches du `when`, le compilateur sait qu'il n'y a pas d'autres possibilités, rendant le `else` superflu. C'est une fonctionnalité très utile pour garantir que vous n'oubliez aucun cas lors de l'ajout de nouvelles options à une énumération ou une classe scellée.

enum class Color { RED, GREEN, BLUE }

fun getColorHex(color: Color): String {
    // Pas besoin de 'else' car tous les cas de Color sont listés
    return when (color) {
        Color.RED -> "#FF0000"
        Color.GREEN -> "#00FF00"
        Color.BLUE -> "#0000FF"
    }
}

sealed class Result
data class Success(val data: String) : Result()
data class Error(val message: String) : Result()
object Loading : Result() // Singleton

fun handleResult(result: Result): String {
    // Pas besoin de 'else' car tous les sous-types de Result sont gérés
    return when (result) {
        is Success -> "Données reçues: ${result.data}"
        is Error -> "Erreur: ${result.message}"
        Loading -> "Chargement en cours..."
    }
}

fun main(){
    println(getColorHex(Color.GREEN)) // #00FF00
    println(handleResult(Success("OK"))) // Données reçues: OK
    println(handleResult(Loading)) // Chargement en cours...
}

Récapitulatif : la polyvalence de `when`

L'expression `when` est l'un des outils de contrôle de flux les plus puissants et flexibles en Kotlin.

  • Elle remplace avantageusement les `switch` traditionnels.
  • Peut être utilisée avec ou sans argument.
  • Supporte les correspondances multiples (`,`), les plages (`in`), les collections (`in`) et les vérifications de type (`is` avec smart casting).
  • Peut être utilisée comme une instruction ou comme une expression (retournant une valeur).
  • Garantit généralement l'exhaustivité via la branche `else` lorsqu'elle est utilisée comme expression (sauf pour les enums/sealed classes).
  • Ne souffre pas du problème de "fall-through" implicite.

Intégrer `when` dans votre boîte à outils Kotlin vous permettra d'écrire une logique conditionnelle complexe de manière claire, concise et sûre.