Contactez-nous

Trouver des éléments : `firstOrNull`, `find`, `any`

Apprenez à rechercher efficacement des éléments dans les collections Kotlin avec `firstOrNull`, `find` (alias de `firstOrNull`) et `any` pour vérifier l'existence.

Introduction : aller au-delà de l'itération simple

Nous avons vu comment traiter tous les éléments d'une collection avec `forEach`, ou comment transformer et filtrer des collections entières avec `map` et `filter`. Cependant, une autre tâche très fréquente consiste à rechercher un ou plusieurs éléments spécifiques au sein d'une collection, en fonction de critères particuliers, ou simplement à vérifier si un élément répondant à ces critères existe.

Plutôt que d'écrire manuellement des boucles `for` avec des conditions `if` et des variables booléennes pour suivre si un élément a été trouvé (ce qui est verbeux et sujet aux erreurs), Kotlin propose des fonctions d'extension dédiées, claires et expressives pour ces scénarios de recherche.

Dans cette section, nous allons nous concentrer sur trois de ces fonctions de recherche fondamentales : `firstOrNull` (et son alias `find`) pour obtenir le premier élément correspondant, et `any` pour vérifier si au moins un élément correspond.

`firstOrNull` et `find` : obtenir le premier élément correspondant (ou null)

La fonction `firstOrNull` est conçue pour trouver le premier élément d'une collection qui satisfait une condition donnée (un prédicat). Si plusieurs éléments correspondent, seule le premier rencontré lors de l'itération est retourné.

Elle prend en argument une lambda (le prédicat) qui retourne `Boolean`. Cette lambda est appliquée à chaque élément. Dès qu'un élément pour lequel la lambda retourne `true` est trouvé, `firstOrNull` retourne cet élément et arrête immédiatement la recherche.

Si aucun élément de la collection ne satisfait la condition (la lambda ne retourne `true` pour aucun élément), `firstOrNull` retourne `null`. C'est la partie "OrNull" de son nom, qui la rend sûre à utiliser car elle ne lèvera jamais d'exception si l'élément n'est pas trouvé.

Le type de retour de `firstOrNull` est donc le type des éléments de la collection, mais rendu nullable (`T?`).

fun main() {
    val numbers = listOf(1, -2, 3, 4, -5, 6)

    // Trouver le premier nombre pair
    val firstEven: Int? = numbers.firstOrNull { it % 2 == 0 } 
    println("Premier nombre pair: $firstEven") // Affiche: Premier nombre pair: -2

    // Trouver le premier nombre négatif
    val firstNegative: Int? = numbers.firstOrNull { it < 0 }
    println("Premier nombre négatif: $firstNegative") // Affiche: Premier nombre négatif: -2

    // Essayer de trouver un nombre supérieur à 10
    val greaterThan10: Int? = numbers.firstOrNull { it > 10 }
    println("Premier nombre > 10: $greaterThan10") // Affiche: Premier nombre > 10: null

    val names = listOf("Alice", "Bob", "Charlie", "Boris")
    // Trouver le premier nom commençant par 'B'
    val nameStartsWithB: String? = names.firstOrNull { it.startsWith("B") }
    println("Premier nom commençant par B: $nameStartsWithB") // Affiche: Premier nom commençant par B: Bob
}

Kotlin fournit également un alias pratique pour `firstOrNull` : la fonction `find`. Elle fait exactement la même chose et est souvent préférée pour sa lisibilité lorsqu'on exprime une intention de recherche.

fun main() {
    val numbers = listOf(1, -2, 3, 4, -5, 6)
    // Utilisation de find (strictement équivalent à firstOrNull)
    val firstNegativeWithFind: Int? = numbers.find { it < 0 }
    println("Premier négatif (avec find): $firstNegativeWithFind") // Affiche: Premier négatif (avec find): -2
}

L'utilisation de `firstOrNull` ou `find` est idéale lorsque vous avez besoin de récupérer un élément spécifique et que vous êtes prêt à gérer le cas où il pourrait ne pas exister (en traitant le résultat `null`, par exemple avec l'opérateur Elvis `?:`).

`any` : vérifier si au moins un élément correspond

Parfois, vous n'avez pas besoin de récupérer l'élément lui-même, mais vous voulez simplement savoir s'il existe au moins un élément dans la collection qui satisfait une certaine condition. Pour cela, on utilise la fonction `any`.

`any` prend également en argument une lambda prédicat (`(T) -> Boolean`). Elle itère sur les éléments de la collection et applique la lambda à chacun.

Dès qu'elle trouve un élément pour lequel la lambda retourne `true`, `any` arrête immédiatement l'itération et retourne `true`. Si elle parcourt toute la collection sans qu'aucun élément ne satisfasse la condition (la lambda retourne toujours `false`), alors `any` retourne `false`.

Le type de retour de `any` est donc toujours `Boolean`.

fun main() {
    val numbers = listOf(1, -2, 3, 4, -5, 6)
    val names = listOf("Alice", "Bob", "Charlie")

    // Y a-t-il au moins un nombre pair ?
    val hasEvenNumber = numbers.any { it % 2 == 0 }
    println("Contient un nombre pair: $hasEvenNumber") // Affiche: Contient un nombre pair: true (-2, 4, 6)

    // Y a-t-il au moins un nombre négatif ?
    val hasNegativeNumber = numbers.any { it < 0 }
    println("Contient un nombre négatif: $hasNegativeNumber") // Affiche: Contient un nombre négatif: true (-2, -5)

    // Y a-t-il au moins un nombre supérieur à 10 ?
    val hasNumberGreaterThan10 = numbers.any { it > 10 }
    println("Contient un nombre > 10: $hasNumberGreaterThan10") // Affiche: Contient un nombre > 10: false

    // Y a-t-il au moins un nom contenant la lettre 'x' ?
    val hasNameWithX = names.any { it.contains("x", ignoreCase = true) }
    println("Contient un nom avec 'x': $hasNameWithX") // Affiche: Contient un nom avec 'x': false

    // Y a-t-il au moins un nom de longueur 5 ?
    val hasNameLength5 = names.any { it.length == 5 }
    println("Contient un nom de longueur 5: $hasNameLength5") // Affiche: Contient un nom de longueur 5: true (Alice)
}

Un avantage clé de `any` (ainsi que de `firstOrNull`) est son efficacité : elle arrête le parcours de la collection dès que le résultat est déterminé. Elle n'a pas besoin de vérifier tous les éléments si elle trouve une correspondance rapidement. `any` est parfaite pour les vérifications d'existence rapides.

Choisir la bonne fonction : `firstOrNull`/`find` vs `any`

Le choix entre ces fonctions dépend de ce que vous voulez obtenir :

  • Utilisez `firstOrNull` ou `find` si vous avez besoin de récupérer l'élément lui-même (le premier qui correspond) et que vous êtes prêt à gérer le cas où il n'existe pas (résultat `null`).
  • Utilisez `any` si vous voulez simplement savoir si oui ou non un élément correspondant existe, sans avoir besoin de récupérer cet élément. Le résultat est un simple `Boolean`.

Il est important de noter qu'il existe aussi la fonction `first`. Elle fonctionne comme `firstOrNull` mais au lieu de retourner `null` si aucun élément ne correspond, elle lève une `NoSuchElementException`. Utilisez `first` uniquement lorsque vous êtes absolument certain qu'un élément correspondant existera, ou si l'absence d'un tel élément est une erreur qui doit interrompre le programme.

fun main() {
    val numbers = listOf(1, 3, 5)

    // Utilisation sûre avec firstOrNull
    val even = numbers.firstOrNull { it % 2 == 0 }
    println("Pair (firstOrNull): $even") // Affiche: Pair (firstOrNull): null

    // Utilisation potentiellement dangereuse avec first
    try {
        val evenWithFirst = numbers.first { it % 2 == 0 }
        println("Pair (first): $evenWithFirst")
    } catch (e: NoSuchElementException) {
        println("Erreur avec first: ${e.message}") // Affiche: Erreur avec first: Collection contains no element matching the predicate.
    }
}

En règle générale, préférez `firstOrNull` (ou `find`) à `first` pour éviter les exceptions inattendues, sauf si la non-existence est véritablement une condition d'erreur exceptionnelle dans votre logique.

Récapitulatif : les outils de recherche essentiels

Pour rechercher des éléments spécifiques dans les collections Kotlin :

  • `firstOrNull { condition }` ou `find { condition }` : Retourne le premier élément pour lequel la `condition` est `true`, ou `null` si aucun ne correspond. Retourne `T?`.
  • `any { condition }` : Retourne `true` s'il existe au moins un élément pour lequel la `condition` est `true`, `false` sinon. Retourne `Boolean`.

Ces fonctions, combinées à la puissance des lambdas pour exprimer les conditions de recherche, rendent le code de recherche dans les collections Kotlin à la fois concis, lisible et sûr.