Comment travailler avec les flux (streams) en Kotlin?
sobes.tech IA
Réponse de l'IA
En Kotlin, pour travailler avec des flux de données (équivalent à Java 8 Streams API), on utilise des fonctions d’extension pour les collections fournies par la bibliothèque de fonctions standard. Ce n’est pas une API séparée, mais un ensemble de méthodes dans kotlin.collections.
Les opérations principales sur les flux se divisent en :
- Opérations intermédiaires : Renvoient une nouvelle collection et sont "paresseuses" (s’exécutent uniquement lors de l’appel d’une opération terminale). Exemples :
filter,map,flatMap,distinct,sorted,take,drop. - Opérations terminales : Finalisent la séquence d’opérations et renvoient un résultat (pas une collection). Déclenchent l’exécution de toutes les opérations intermédiaires. Exemples :
forEach,reduce,fold,count,sum,average,min,max,toList,toSet,toMap.
Il existe aussi le concept de séquences (sequences), qui offre une paresse explicite. Les séquences traitent les éléments un par un selon le besoin, ce qui peut être plus efficace pour de grandes collections ou de longues chaînes d’opérations :
- Création d’une séquence :
collection.asSequence() - Conversion en collection :
sequence.toList()
// Exemple d’utilisation des fonctions standard pour le traitement en flux
fun processCollection() {
val numbers = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
val evenSquares = numbers
.filter { it % 2 == 0 } // Opération intermédiaire : filtrer les nombres pairs
.map { it * it } // Opération intermédiaire : mettre au carré
println(evenSquares) // Opération terminale : afficher la collection (exécute filter et map)
val sumOfOdd = numbers
.filter { it % 2 != 0 } // Intermédiaire
.sum() // Terminal (calcule la somme des impairs)
println(sumOfOdd)
}
// Exemple d’utilisation des séquences
fun processSequence() {
val largeCollection = (1..1_000_000).toList()
// Avec collections (pas paresseuses) : filter et map s’exécutent pour tous les éléments immédiatement
val resultCollection = largeCollection
.filter { it % 2 == 0 }
.map { it * 2 }
.take(10) // take(10) s’exécute après filter et map pour tous
println("Taille de la collection résultat : ${resultCollection.size}")
// Avec séquences (paresseuses) : filter, map et take s’exécutent élément par élément
val resultSequence = largeCollection.asSequence()
.filter { it % 2 == 0 } // filter s’exécute pour chaque élément en ordre
.map { it * 2 } // map s’exécute pour chaque élément passant le filtre
.take(10) // take(10) arrête le traitement après 10 éléments
println("Taille de la séquence résultat : ${resultSequence.toList().size}") // toList() déclenche l’exécution de la séquence
}
fun main() {
processCollection()
processSequence()
}
Tableau comparatif des fonctions standard et des séquences concernant la paresse :
| Caractéristique | Fonctions standard des collections | Séquences |
|---|---|---|
| Paresse | Paresse implicite pour les opérations intermédiaires, mais l’opération terminale traite toute la chaîne intermédiaire | Paresse explicite : les opérations s’exécutent élément par élément selon le besoin |
| Efficacité | Peut être moins efficace pour de grandes collections et longues chaînes d’opérations à cause des collections intermédiaires | Plus efficace pour de grandes collections et longues chaînes d’opérations |
| Application | Convient pour de petites et moyennes collections | Convient pour de grandes collections ou opérations pouvant s’arrêter tôt (take, find, first, any, all, none) |
L’utilisation des fonctions standard sur les collections est généralement suffisante et plus concise. Les séquences sont recommandées lorsque la paresse explicite est nécessaire pour optimiser la performance lors du traitement de grands volumes de données.