Introduits avec Java 8, les Streams Java révolutionnent le traitement des données en offrant une approche fonctionnelle élégante. Cette API permet de manipuler les collections de manière déclarative grâce à des opérations en chaîne, simplifiant ainsi le code tout en améliorant sa lisibilité.
Structure des opérations
L'API Stream dans Java fonctionne selon un modèle en trois phases distinctes :
✏️ Création du Stream
Plusieurs façons de créer un Stream :
🔎 Opérations intermédiaires
Ces opérations transforment le Stream et retournent un nouveau Stream :
⌛ Opération terminale
Une opération qui produit un résultat ou un effet de bord :
Caractéristiques importantes
L'API Stream possède des caractéristiques innovantes qui ont contribuées à son succès par rapport aux versions Java précédentes.
🦥 Évaluation paresseuse
Si l'on évalue le code précédent, rien ne s'affichera ! En effet, avec l'API Stream, aucune opération n'est exécutée jusqu'à l'appel d'une opération terminale.
🕵️ Pipeline de traitement
Le fait que chaque opération intermédiaire retourne un Stream permet de chainer les appels et ainsi de créer des pipelines de traitement avec une lisibilité de code accrue.
🚀 Traitement parallèle
⚠️ Attention aux opérations bloquantes dans les Streams parallèles
Lors de l'utilisation de parallelStream()
ou stream().parallel()
, il est crucial d'éviter les opérations bloquantes qui pourraient dégrader les performances :
Les opérations bloquantes comme les appels synchrones à des APIs, les opérations I/O peuvent :
- Monopoliser les threads du Common ForkJoinPool
- Réduire significativement les performances
- Créer des deadlocks potentiels
Pour les opérations asynchrones, privilégiez l'utilisation de CompletableFuture,
de frameworks réactifs ou l'utilisation des Virtual Thread.
Les streams parallèles, et plus largement l'utilisation des threads du Common ForkJoinPool sont réservés à des tâches de calcul.
📚 Source de données et Spliterator
Les Streams s'appuient sur un mécanisme de "push" où la source de données pousse les éléments vers le Stream via un Spliterator. Ce dernier est responsable de la traversée et du partitionnement des éléments de la source.
Connexion avec une base de données
Le try-with-resources
est important pour s'assurer que la session Hibernate est correctement fermée après l'utilisation du Stream.
L'avantage par rapport à un findAll qui renverrai une liste est qu'ici, nous n'avons qu'un seul objet en mémoire à l'instant T.
🗝️ Points clés à retenir
- Usage Unique : Un Stream ne peut être utilisé qu'une seule fois. Après une opération terminale, le Stream est fermé.
- Immutabilité : Les opérations de Stream ne modifient pas la source de données originale.
- Chaînage : Les opérations peuvent être chaînées pour créer des pipelines de traitement complexes.
- Short-Circuiting : Certaines opérations (comme findFirst(), limit()) peuvent arrêter le traitement avant de parcourir tous les éléments.
Exemple complet
Cette structure permet une manipulation efficace et expressive des données, particulièrement adaptée aux traitements complexes sur des collections.
Conclusion
Les Streams Java, introduits avec Java 8, ont transformé la manière dont les développeurs manipulent les collections de données. Grâce à une approche fonctionnelle, des opérations en chaîne et un traitement optimisé, cette API permet d’écrire un code plus lisible, concis et performant.
L’évaluation paresseuse et le support du traitement parallèle offrent des gains significatifs en efficacité, à condition de bien maîtriser leur utilisation pour éviter les pièges des opérations bloquantes.
Que ce soit pour le filtrage, la transformation ou la réduction des données, les Streams constituent un outil puissant et incontournable pour tout développeur Java moderne. 🚀
👉 Et vous, utilisez-vous déjà les Streams dans vos projets ? 😊
Si vous souhaitez en savoir plus sur l'histoire de Java, découvrez cet article 👇