Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 41

Traitement de Collections avec l'API Stream : Opérations Intermédiaires

Recommended resources

Bonjour ! Dans la leçon précédente, vous avez appris à utiliser les interfaces fonctionnelles standards de Java : Predicate, Function, Consumer et Supplier. Vous avez vu comment les expressions lambda permettent de fournir des implémentations concises pour ces interfaces.

Aujourd'hui, nous allons assembler ces briques fondamentales pour explorer l'une des fonctionnalités les plus puissantes et les plus utilisées du Java moderne : l'API Stream. L'objectif est d'apprendre à traiter des collections de données de manière déclarative en utilisant des opérations intermédiaires comme filter, map et sorted. Vous découvrirez comment enchaîner ces opérations pour créer des pipelines de traitement de données élégants et lisibles, une compétence essentielle pour tout développeur backend Java.

1. Qu'est-ce que l'API Stream ?

Jusqu'à présent, pour manipuler une collection, vous auriez probablement utilisé des boucles (for, while), comme vous le feriez en Python ou en PHP. L'API Stream, introduite dans Java 8, propose une approche radicalement différente.

Un Stream n'est pas une structure de données qui stocke des éléments ; c'est une séquence d'éléments provenant d'une source (comme une List ou un Set) qui supporte des opérations d'agrégation. L'idée est de décrire ce que vous voulez faire (le résultat) plutôt que comment le faire (les boucles et les conditions).

Imaginez une chaîne de montage (un pipeline) : les données brutes entrent d'un côté, passent par plusieurs stations (opérations) qui les filtrent, les transforment ou les trient, et un produit fini en sort à la fin.

Cette image illustre parfaitement le concept de pipeline de Stream. Les données traversent séquentiellement des opérations comme `filter` et `map` avant d'être assemblées par une opération finale comme `collect`.

Pour bien saisir la différence, étudions un exemple simple.

JAVA POO: API Stream - Terence Hill Nzidjouo Fonji

Cet article de Terence Hill Nzidjouo Fonji sur Medium introduit l'API Stream avec une comparaison très parlante.

Lisez la section "Qu’est-ce que l’API Stream ?" et concentrez-vous sur l'exemple basique qui compare la méthode traditionnelle avec boucles (Avant l’API Stream) à la nouvelle approche (Avec l’API Stream). Notez comme le code devient plus concis et expressif.

Comme vous l'avez vu, le code utilisant les Streams est plus déclaratif. Il exprime une séquence d'actions :

  1. Prendre la liste comme un stream().
  2. Puis, filter() les nombres pairs.
  3. Puis, map() chaque nombre à son carré.
  4. Puis, les sorted() (trier).
  5. Enfin, collect() le résultat dans une nouvelle liste.

Les opérations comme filter, map, et sorted sont appelées opérations intermédiaires. Elles sont "paresseuses" (lazy), ce qui signifie qu'elles ne s'exécutent pas immédiatement. Elles construisent simplement le plan du pipeline. L'exécution réelle n'est déclenchée que par une opération terminale, comme collect(). Aujourd'hui, nous nous concentrons sur les opérations intermédiaires.


2. Les Opérations Intermédiaires Clés

Explorons les trois opérations intermédiaires les plus courantes. Pour chaque opération, vous verrez qu'elle prend en paramètre l'une des interfaces fonctionnelles que vous avez étudiées.

filter(Predicate<T> predicate) : La Sélection

L'opération filter est utilisée pour sélectionner des éléments d'un Stream qui respectent une certaine condition. Elle prend un Predicate en argument, que vous fournirez le plus souvent sous forme d'expression lambda. Seuls les éléments pour lesquels le prédicat renvoie true sont conservés dans le flux.

map(Function<T, R> mapper) : La Transformation

L'opération map transforme chaque élément du Stream en un autre objet. Elle prend une Function en argument. C'est l'outil idéal pour extraire une propriété d'un objet (par exemple, obtenir le nom de tous les produits) ou pour convertir les objets d'un type à un autre.

sorted() : Le Tri

L'opération sorted trie les éléments du Stream. Utilisée sans argument, elle applique l'ordre naturel des éléments (alphabétique pour les String, numérique pour les Integer). Vous pouvez également lui fournir un Comparator (que nous avons vu avec les lambdas) pour un tri personnalisé.

La ressource suivante détaille chacune de ces opérations avec des exemples de code clairs.

JAVA POO: API Stream - Terence Hill Nzidjouo Fonji

Retournons à l'article sur Medium pour une exploration détaillée des opérations intermédiaires.

Lisez attentivement la section "2. Opérations Intermédiaires". Pour chaque opération (filter, map, distinct, sorted, limit, skip), analysez l'exemple de code et l'explication "Ce que ça fait". Concentrez-vous principalement sur filter, map, et sorted, qui sont les plus fondamentales.

Pour visualiser le processus, regardez comment un expert manipule un Stream dans cette vidéo.

Java POO : Tutoriel API Stream (Filter / AnyMatch / Collectors)

Cette vidéo de JirAWS montre l'utilisation pratique de filter et map.

Regardez d'abord la démonstration de filter : l'exemple avec filter. Observez comment le Predicate (la condition h.getAge() >= 30) est utilisé pour ne garder que certains humains. Ensuite, regardez l'enchaînement avec map : l'exemple avec map. Notez comment map est inséré dans la chaîne pour transformer les objets Human filtrés en leur numéro de sécurité sociale (Long).


3. Enchaîner les Opérations : La Puissance du Pipeline

La véritable force des Streams réside dans la capacité à enchaîner ces opérations pour construire des pipelines de traitement complexes, tout en gardant le code lisible.

Cette illustration montre le flux de données à travers une chaîne typique d'opérations : la source est transformée en Stream, puis filtrée, triée, mappée et enfin collectée.

Considérons un scénario réaliste pour un développeur backend. Vous avez une liste d'utilisateurs et vous voulez obtenir les noms, triés par ordre alphabétique, de tous les utilisateurs majeurs.

JAVA POO: API Stream - Terence Hill Nzidjouo Fonji

La section suivante de l'article sur Medium met en pratique tout ce que nous venons de voir avec un cas d'usage concret.

Lisez la section "Exemple 1 : Filtrer et Transformer des Données". Analysez la version "Code avec Stream" et décomposez mentalement chaque étape de la chaîne : people.stream(): Obtention du flux. .filter(person -> person.getAge() > 18): Sélection des personnes majeures. Le Stream ne contient plus que des adultes. .map(Person::getName): Transformation de chaque objet Person en son String (nom). Le Stream contient maintenant des chaînes de caractères. .sorted(): Tri des noms par ordre alphabétique. .collect(Collectors.toList()): Opération terminale qui déclenche le traitement et rassemble les résultats dans une List<String>.

Ce style de programmation est non seulement plus concis, mais il est aussi plus expressif et moins sujet aux erreurs que les boucles imbriquées.


4. Exercice Pratique : Gérer un Catalogue de Produits

Mettons en pratique ces nouvelles connaissances. Reprenons notre classe Product et imaginons que vous devez travailler sur un catalogue de produits pour un site e-commerce.

Votre mission :
Écrivez un pipeline de Stream pour obtenir la liste des noms des produits qui coûtent plus de 50€, triés par prix croissant.

Code de départ :
Copiez-collez ce code dans une nouvelle classe Main de votre projet.

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import java.util.Comparator;

class Product {
    private String name;
    private double price;

    public Product(String name, double price) {
        this.name = name;
        this.price = price;
    }

    public String getName() { return name; }
    public double getPrice() { return price; }

    @Override
    public String toString() {
        return "Product{" + "name='" + name + '\'' + ", price=" + price + '}';
    }
}

public class Main {
    public static void main(String[] args) {
        List<Product> products = Arrays.asList(
            new Product("Souris Gamer", 49.99),
            new Product("Écran 27 pouces", 349.00),
            new Product("Clavier Mécanique", 129.50),
            new Product("Tapis de souris XL", 24.90),
            new Product("Webcam 4K", 199.99)
        );

        // VOTRE PIPELINE DE STREAM ICI
        // 1. Créez un stream à partir de la liste 'products'.
        // 2. Filtrez les produits pour ne garder que ceux dont le prix est supérieur à 50.0.
        // 3. Triez les produits filtrés par prix croissant. (Indice: Comparator.comparing(...))
        // 4. Mappez chaque objet Product à son nom (String).
        // 5. Collectez les résultats dans une List<String>.
        List<String> expensiveProductNames = /* ... VOTRE CODE ... */;

        System.out.println("Noms des produits chers, triés par prix :");
        expensiveProductNames.forEach(System.out::println);
        // Résultat attendu :
        // Clavier Mécanique
        // Webcam 4K
        // Écran 27 pouces
    }
}

Indices :

  • Pour le tri par prix, vous pouvez utiliser sorted(Comparator.comparing(Product::getPrice)).
  • L'ordre des opérations est important : si vous mappez avant de trier par prix, vous perdrez l'information du prix ! Vous devez trier les objets Product avant de les transformer en String.
Cliquez ici pour voir la solution
List<String> expensiveProductNames = products.stream()
    .filter(p -> p.getPrice() > 50.0) // Filtre les produits de plus de 50€
    .sorted(Comparator.comparing(Product::getPrice)) // Trie par prix croissant
    .map(Product::getName) // Transforme les Product en leur nom (String)
    .collect(Collectors.toList()); // Collecte les noms dans une liste

Conclusion

Félicitations ! Vous venez de faire vos premiers pas avec l'API Stream, une compétence fondamentale pour tout développeur Java moderne.

Points clés à retenir :

  • L'API Stream permet de traiter les collections de manière déclarative.
  • Un pipeline de Stream est composé d'une source, de zéro ou plusieurs opérations intermédiaires (filter, map, sorted...), et d'une opération terminale.
  • Les opérations intermédiaires sont paresseuses (lazy) : elles ne s'exécutent que lorsqu'une opération terminale est appelée.
  • filter(Predicate) sélectionne les éléments.
  • map(Function) transforme les éléments.
  • sorted(Comparator) ordonne les éléments.

Vous avez appris à construire la "chaîne de montage". Dans la prochaine leçon, nous nous concentrerons sur la fin de la chaîne : les opérations terminales. Vous apprendrez à agréger les résultats de vos pipelines de différentes manières avec collect, forEach, reduce et d'autres, pour produire des résultats finaux (une liste, une map, une somme, etc.).