Contactez-nous

Opérateur Elvis : `?:` (valeur par défaut)

Apprenez à utiliser l'opérateur Elvis `?:` en Kotlin pour fournir une valeur par défaut lorsqu'une expression nullable est `null`. Simplifiez votre code Null Safety.

Le besoin d'une valeur de secours

L'opérateur d'appel sécurisé `?.` est excellent pour exécuter une action ou accéder à une propriété uniquement si une référence n'est pas nulle, et retourner `null` sinon. Cependant, il arrive souvent que, si une valeur est `null`, nous ne voulions pas propager ce `null` mais plutôt utiliser une valeur par défaut à la place.

Par exemple, imaginez vouloir afficher le nom d'un utilisateur s'il est défini, et afficher "Guest" sinon. Avec les outils vus jusqu'à présent, on pourrait écrire :

fun getUserDisplayName(name: String?): String {
    val displayName: String? = name // name est nullable
    if (displayName != null) {
        return displayName
    } else {
        return "Guest"
    }
}

// Ou plus court avec l'appel sécurisé mais toujours un peu verbeux
fun getUserDisplayNameShort(name: String?): String {
    val finalName = name?.uppercase() // Ceci retourne String?
    if (finalName != null) {
        return finalName
    } else {
        return "GUEST" // Valeur par défaut si name était null
    }
}

Ces solutions fonctionnent, mais Kotlin, fidèle à son objectif de concision et d'expressivité, propose un opérateur dédié pour ce scénario très courant : l'opérateur Elvis.

Présentation de l'opérateur Elvis `?:`

L'opérateur Elvis s'écrit `?:`. Son nom vient de sa ressemblance (en penchant la tête sur le côté) avec l'émoticône d'Elvis Presley et sa fameuse coiffure `:)`. Sa syntaxe est la suivante :

`expression1 ?: expression2`

Son fonctionnement est très simple :

  • L'opérateur évalue d'abord l'expression à sa gauche (`expression1`).
  • Si le résultat de `expression1` n'est pas `null`, alors ce résultat non-nul est la valeur de toute l'expression `?:`.
  • Si le résultat de `expression1` est `null`, alors l'opérateur évalue l'expression à sa droite (`expression2`), et le résultat de `expression2` devient la valeur de toute l'expression `?:`.

Essentiellement, `?:` dit : "Utilise la valeur de gauche si elle n'est pas nulle, sinon utilise la valeur de droite".

fun main() {
    val name: String? = "Alice"
    val noName: String? = null

    // Utilisation de l'opérateur Elvis
    val displayName1 = name ?: "Guest" // name n'est pas null, donc displayName1 = "Alice"
    val displayName2 = noName ?: "Guest" // noName est null, donc displayName2 = "Guest"

    println("Nom 1: $displayName1") // Affiche: Nom 1: Alice
    println("Nom 2: $displayName2") // Affiche: Nom 2: Guest

    val favoriteColor: String? = null
    val defaultColor = "Bleu"
    val userColor = favoriteColor ?: defaultColor // favoriteColor est null, userColor = "Bleu"
    println("Couleur: $userColor") // Affiche: Couleur: Bleu
}

Notez un avantage clé : si l'expression de droite (la valeur par défaut) est non-nullable (comme "Guest" ou `defaultColor`), alors le type résultant de l'opérateur Elvis sera également non-nullable. `displayName1`, `displayName2`, et `userColor` sont de type `String`, pas `String?`.

Combinaison avec l'appel sécurisé `?.`

L'opérateur Elvis `?:` est très souvent utilisé en combinaison avec l'opérateur d'appel sécurisé `?.`. Cela permet d'essayer d'accéder à un membre d'une variable nullable et de fournir une valeur par défaut si la variable initiale était nulle ou si l'un des appels dans la chaîne échoue.

fun main() {
    val s1: String? = "Kotlin"
    val s2: String? = null

    // Obtenir la longueur ou 0 si la chaîne est nulle
    // s1?.length -> 6 (Int?)
    // 6 ?: 0 -> 6 (Int)
    val length1 = s1?.length ?: 0

    // s2?.length -> null (Int?)
    // null ?: 0 -> 0 (Int)
    val length2 = s2?.length ?: 0

    println("Longueur 1: $length1") // Affiche: Longueur 1: 6
    println("Longueur 2: $length2") // Affiche: Longueur 2: 0

    // Chaînage
data class Street(val name: String?)
data class Address(val street: Street?)
data class User(val address: Address?)

    val userWithStreet = User(Address(Street("Main St")))
    val userNoStreet = User(Address(null))
    val userNoAddress: User? = null

    // Obtenir le nom de la rue ou "Unknown" si non disponible
    val streetName1 = userWithStreet?.address?.street?.name ?: "Unknown"
    val streetName2 = userNoStreet?.address?.street?.name ?: "Unknown"
    val streetName3 = userNoAddress?.address?.street?.name ?: "Unknown"

    println("Rue 1: $streetName1") // Affiche: Rue 1: Main St
    println("Rue 2: $streetName2") // Affiche: Rue 2: Unknown
    println("Rue 3: $streetName3") // Affiche: Rue 3: Unknown
}

Cette combinaison `?. ... ?: ...` est un idiome très puissant et courant en Kotlin pour gérer la nullabilité et fournir des valeurs de secours de manière concise et sûre, tout en obtenant souvent un résultat final non-nullable.

Utilisation avec `return`, `throw` et autres expressions

L'expression à droite de l'opérateur Elvis `?:` n'est pas limitée à une simple valeur littérale ou une variable. Elle peut être n'importe quelle expression, y compris des instructions de contrôle de flux comme `return` ou `throw`.

C'est particulièrement utile pour vérifier des préconditions ou gérer des erreurs en début de fonction : si une valeur requise est `null`, on peut immédiatement sortir de la fonction ou lancer une exception.

// Exemple avec return
fun processName(name: String?) {
    val nameToProcess = name ?: return // Si name est null, sort de la fonction ici

    // Ici, on est sûr que nameToProcess est non-null (type String)
    println("Traitement du nom: ${nameToProcess.uppercase()}")
}

// Exemple avec throw
class UserNotFoundException(message: String) : Exception(message)

fun getUserStatus(userId: String?): String {
    val id = userId ?: throw IllegalArgumentException("User ID cannot be null")

    // Simule la recherche
    val userFound = id == "admin" // Trouvé seulement si id est "admin"

    return if (userFound) {
        "Active"
    } else {
        throw UserNotFoundException("User with ID '$id' not found")
    }
}

fun main() {
    processName("Bob") // Affiche: Traitement du nom: BOB
    processName(null)   // N'affiche rien, car return est exécuté

    try {
        println("Statut admin: ${getUserStatus("admin")}") // Affiche: Statut admin: Active
        println("Statut guest: ${getUserStatus("guest")}")
    } catch (e: Exception) {
        println("Erreur: ${e.message}") // Affiche: Erreur: User with ID 'guest' not found
    }

    try {
        getUserStatus(null)
    } catch (e: Exception) {
        println("Erreur: ${e.message}") // Affiche: Erreur: User ID cannot be null
    }
}

Utiliser `return` ou `throw` à droite de `?:` est un motif très efficace pour valider les entrées ou les états requis et simplifier le reste de la logique de la fonction, en garantissant que les variables utilisées par la suite sont non nulles.

Récapitulatif : l'opérateur Elvis pour des valeurs sûres

L'opérateur Elvis `?:` est un outil essentiel dans la boîte à outils Null Safety de Kotlin.

  • Il fournit une valeur de secours (`expression2`) lorsqu'une expression nullable (`expression1`) évalue à `null`. Syntaxe : `expression1 ?: expression2`.
  • Il est très souvent combiné avec l'opérateur d'appel sécurisé `?.` pour fournir une valeur par défaut après une tentative d'accès : `nullableRef?.member ?: defaultValue`.
  • Il permet souvent d'obtenir un résultat final non-nullable si la valeur par défaut est non-nullable.
  • L'expression à droite peut être une valeur, une variable, un appel de fonction, ou même une instruction de contrôle comme `return` ou `throw`.

Maîtriser l'opérateur Elvis vous permet d'écrire du code Kotlin encore plus concis, lisible et robuste face à l'absence potentielle de valeurs.