Le traitement de données dans un style fonctionnel vous permet d'explorer l'API Stream qui vous permet d'écrire du code puissant qui traite une collection de données de manière déclarative.
Il est tentant de voir un Stream comme une collection. Les notions de collections et de Stream concernent toutes les deux une séquence d'éléments. La différence entre collection et Stream est la suivante : une collection permet de stocker cette séquence d'éléments et un Stream permet d'exécuter des opérations sur cette séquence d'éléments.
A la lecture de cet article, vous aurez une base de connaissances qui vont vous permettre de comprendre les Stream et comment vous pouvez les utiliser dans votre code pour traiter une collection de données de manière concise et efficace.
A la fin on fera un détour sur les nouveautés de l'API Stream de Java 8+.
Cas d’utilisation:
Imaginons que nous voulons créer une collection de plats afin de représenter un menu, puis la parcourir pour additionner les calories de chaque plat. Nous voulons traiter la collection pour sélectionner uniquement des plats faibles en calories pour un menu spécial sain.
Prérequis : maitrise des lambda expressions dans Java 8
Collections est l’API la plus utilisée en java. Que feriez-vous sans collections ? Presque chaque application java fabrique et traite des collections. Elles vous permettent de regrouper et traiter les données. Les collections sont nécessaires pour presque toutes les applications Java. La manipulation des collections est loin d’être parfaite. Alors, que pourraient faire les concepteurs de Java pour manipuler facilement les collections ? Vous avez peut-être deviné : la réponse est : Stream.
Ce code écrit en java 7. Ce code utilise un conteneur jetable. Ce conteneur jetable est : la variable platsFaibleEnCalories. |
Le même écrit en Java 8 en utilisant les Stream. Pour exploiter l’architecture multicoeurs et exécuter ce code en parallèle, il suffit de remplacer stream() par parallelStream() |
|
Les opérations intermédiaires sont effectuées de façon lazy et renvoient un nouveau Stream, ce qui crée une succession de Stream que l’on appelle Stream pipelines. Elles ne réalisent aucun traitement tant que l'opération terminale n'est invoquée.
Quand une opération terminale sera appelée on va alors traverser tous les Stream créés par les opérations intermédiaires, appliquer les différentes opérations aux données puis ajouter l’opération terminale. Dès lors, tous les Stream seront dit consommés, ils seront détruits et ne pourront plus être utilisés. Elles renvoient une valeur différente d'un Stream (ou pas de valeur) et ferme le Stream à la fin de leur exécution.
Opération intermédiaire : filter,map,limit Opération terminal : collect |
Dans l’exemple précédent, plusieurs optimisations sont mise en œuvre par le Stream. L’opération limit(2) renvoie un Stream qui contient au maximum 2 éléments.
Les opérations n'itèrent pas individuellement sur les éléments du Stream. Ceci permet d'optimiser au mieux les traitements à réaliser pour obtenir le résultat de manière efficiente.
L'utilisation d'un Stream implique généralement trois choses :
On veut créer une liste des 8 premières personnes dont le nom commence par un 'RO'.
Cet exemple utilise trois opérations intermédiaires :
L'opération terminale collect() transforme les résultats de l'exécution dans une collection de type List.
Pour filtrer des données, un Stream propose plusieurs opérations :
Pour rechercher une correspondance avec des éléments, un Stream propose plusieurs opérations :
Pour transformer des données, un Stream propose plusieurs opérations :
Pour réduire les données et produire un résultat, un Stream propose plusieurs opérations :
Les opérations map() et flatMap() servent toutes les deux à réaliser des transformations mais il existe une différence majeure :
L'opération flatMap() transforme chaque élément en un Stream d'autres éléments : ainsi chaque élément va être transformé en zéro, un ou plusieurs autres éléments. Le contenu des Streams issus du résultat de la transformation de chaque objet est agrégé pour constituer le Stream retourné par la méthode flatMap().
L'opération flatMap() attend en paramètre une Function qui renvoie un Stream. La Function est appliquée sur chaque élément qui produit chacun un Stream. Le résultat renvoyé par la méthode est un Stream qui est l'agréation de tous les Streams produits par les invocations de la Function.
Certaines utilisations de l’API Stream peuvent rendre le code moins lisible et compréhensible ou moins performant.
Parcours des éléments :
Inutile d’avoir recours à un Stream pour appliquer un traitement sur les éléments d’une collection. Cela est d’autant plus vrai si aucune opération intermédiaire n’est utilisée dans le Stream.
Dans l’exemple précédent, il n’est pas recommandé de faire recours à un Stream. Si aucune opération intermédiaire est à utiliser, il est préférable d’utilisé la méthode forEach lorsque l’on dois parcourir les éléments d’une collection. L’exemple précédent deviendra :
Détermination du nombre d’éléments d’une collection :
Il n’est pas nécessaire d’utiliser un Stream pour uniquement déterminer le nombre d’éléments d’une collection.
Dans l’exemple précédent, il n’est utile d’avoir recours à un Stream. Il est préférable d’utiliser la méthode size() de l’interface Collection comme le stipule l’exemple suivant :
La vérification de la présence d’un élément dans une collection :
Il n’est pas nécessaire d’utiliser une Stream pour uniquement vérifier la présence d’un élément dans une collection.
Dans l’exemple précédent, il est inutile d’utiliser une Stream, il est préférable d’utiliser la méthode contains() de l’interface collection comme l’illustre l’exemple suivant :
Quatre nouvelles méthodes ont vu le jour dans l’API Stream, il s’agit de takeWhile(), dropWhile(),ofNullable() et iterate().
takeWhile : Retourne des éléments d'une séquence tant que la condition spécifiée a la valeur true, puis ignore les éléments restants.
L'opération dropWhile supprimera des éléments tandis que le prédicat donné pour un élément renvoie true et arrête de supprimer le false du premier prédicat.
L'opération itérate modifie légèrement cette méthode en ajoutant un prédicat qui s'applique aux éléments pour déterminer quand le Stream doit se terminer. Son utilisation est très pratique et concise.
Références :
https://www.baeldung.com/java-9-stream-api
https://docs.oracle.com/javase/9/docs/api/java/util/stream/Stream.html