CodeGym /Cours /JAVA 25 SELF /Collecteurs avancés

Collecteurs avancés

JAVA 25 SELF
Niveau 31, Leçon 4
Disponible

1. Introduction

Lorsque vous travaillez avec des collections via le Stream API, il ne suffit pas souvent de simplement regrouper les éléments : vous voulez aussi agir immédiatement dessus : les transformer, les filtrer, les collecter dans un autre type de collection. Pour cela, Java propose des collecteurs downstream — des collecteurs imbriqués appliqués à chaque groupe ou partie de données.

Qu’est-ce qu’un collecteur downstream ?

C’est un collecteur qui s’applique au résultat d’un regroupement ou d’un partitionnement. Par exemple, vous pouvez regrouper des étudiants par cours avec groupingBy, et à l’intérieur de chaque groupe ne collecter que les noms ou seulement les étudiants d’excellence (par exemple, avec un seuil 4.5 de GPA).

Exemples

mapping

Permet de transformer les éléments du groupe avant la collecte.

Map<Integer, List<String>> namesByCourse = students.stream()
    .collect(Collectors.groupingBy(
        Student::getCourse,
        Collectors.mapping(Student::getName, Collectors.toList())
    ));
  • Nous regroupons les étudiants par cours.
  • Pour chaque groupe, nous ne collectons que les noms (pas les objets entiers).

filtering

Permet de filtrer les éléments à l’intérieur du groupe (par exemple, ne garder que les GPA >= 4.5).

Map<Integer, List<Student>> honorsByCourse = students.stream()
    .collect(Collectors.groupingBy(
        Student::getCourse,
        Collectors.filtering(s -> s.getGpa() >= 4.5, Collectors.toList())
    ));

Dans chaque groupe, nous ne gardons que les étudiants d’excellence.

flatMapping

Permet « d’aplatir » les collections imbriquées à l’intérieur des groupes.

Map<String, Set<String>> tagsByAuthor = books.stream()
    .collect(Collectors.groupingBy(
        Book::getAuthor,
        Collectors.flatMapping(
            book -> book.getTags().stream(),
            Collectors.toSet()
        )
    ));

Pour chaque auteur, nous collectons les tags uniques de tous ses livres.

partitioningBy с downstream

Fonctionne comme groupingBy, mais divise en deux groupes selon une condition booléenne.

Map<Boolean, List<String>> namesByPassed = students.stream()
    .collect(Collectors.partitioningBy(
        s -> s.getGpa() >= 3.0,
        Collectors.mapping(Student::getName, Collectors.toList())
    ));

Nous séparons les étudiants ayant validé et ceux n’ayant pas validé ; à l’intérieur de chaque groupe, seulement les noms.

2. teeing : agrégation simultanée par deux collecteurs

Il arrive que l’on doive calculer plusieurs agrégats en parallèle sur un flux : par exemple la somme et la moyenne, ou le minimum et le maximum. Pour cela, à partir de Java 12+ est apparu le collecteur teeing.

Comment fonctionne teeing ?

Vous passez deux collecteurs et une fonction qui fusionne leurs résultats.

Syntaxe :

Collectors.teeing(collector1, collector2, (result1, result2) -> ...)

Exemples

Minimum + maximum

Optional<MinMax> minMax = numbers.stream()
    .collect(Collectors.teeing(
        Collectors.minBy(Integer::compareTo),
        Collectors.maxBy(Integer::compareTo),
        (min, max) -> min.isPresent() && max.isPresent() ? new MinMax(min.get(), max.get()) : null
    ));

Nous trouvons simultanément le minimum et le maximum.

Somme + moyenne

Result result = numbers.stream()
    .collect(Collectors.teeing(
        Collectors.summingInt(Integer::intValue),
        Collectors.averagingInt(Integer::intValue),
        (sum, avg) -> new Result(sum, avg)
    ));

Nous obtenons un objet contenant la somme et la moyenne.

Exemple : rapport sur les salaires

SalaryStats stats = employees.stream()
    .collect(Collectors.teeing(
        Collectors.summingInt(Employee::getSalary),
        Collectors.averagingInt(Employee::getSalary),
        SalaryStats::new
    ));

Dans SalaryStats, on stocke à la fois la somme et la moyenne.

3. toUnmodifiableList/Set/Map et collectingAndThen pour le « gel »

Dans les versions modernes de Java, il existe des collections qui ne peuvent pas être modifiées après leur création — immuables (unmodifiable). C’est pratique pour les API où il est important que le résultat ne puisse pas être modifié par inadvertance.

toUnmodifiableList/Set/Map

  • Renvoient une collection immuable.
  • Toute tentative d’ajouter/supprimer un élément lèvera une UnsupportedOperationException.

Exemples :

List<String> names = students.stream()
    .map(Student::getName)
    .collect(Collectors.toUnmodifiableList());
Map<Integer, Student> byId = students.stream()
    .collect(Collectors.toUnmodifiableMap(Student::getId, Function.identity()));

collectingAndThen

Permet d’appliquer une fonction au résultat d’un collecteur — par exemple, « geler » la collection.

List<String> names = students.stream()
    .map(Student::getName)
    .collect(Collectors.collectingAndThen(
        Collectors.toList(),
        Collections::unmodifiableList
    ));

Nous collectons d’abord dans une liste ordinaire, puis nous la rendons immuable.

Exemple avec Set :

Set<String> tags = books.stream()
    .flatMap(book -> book.getTags().stream())
    .collect(Collectors.collectingAndThen(
        Collectors.toSet(),
        Set::copyOf // Java 10+
    ));

4. Cas de pipelines

Rapports et statistiques par segments

Avec des collecteurs avancés, on peut construire des rapports et des statistiques complexes « en une seule ligne ».

Exemple : salaire moyen par département

Map<String, Double> avgSalaryByDept = employees.stream()
    .collect(Collectors.groupingBy(
        Employee::getDepartment,
        Collectors.averagingInt(Employee::getSalary)
    ));

Exemple : top 3 des produits les plus chers par catégorie

Map<String, List<Product>> top3ByCategory = products.stream()
    .collect(Collectors.groupingBy(
        Product::getCategory,
        Collectors.collectingAndThen(
            Collectors.toList(),
            list -> list.stream()
                        .sorted(Comparator.comparing(Product::getPrice).reversed())
                        .limit(3)
                        .toList()
        )
    ));

Résultat immuable comme contrat d’API

Si votre méthode renvoie une collection non modifiable, cela évite des erreurs accidentelles et rend l’API plus robuste.

public List<String> getTags() {
    return tags.stream()
        .collect(Collectors.toUnmodifiableList());
}

L’utilisateur ne pourra pas faire getTags().add("nouveau tag") — une exception sera levée.

Exemple : rapport avec plusieurs agrégats (teeing)

public SalaryReport getSalaryReport(List<Employee> employees) {
    return employees.stream()
        .collect(Collectors.teeing(
            Collectors.averagingInt(Employee::getSalary),
            Collectors.summingInt(Employee::getSalary),
            SalaryReport::new
        ));
}

Dans SalaryReport, on stocke à la fois le salaire moyen et le salaire total.

5. Erreurs courantes et subtilités

Erreur n° 1 : oubli de l’immuabilité. Si vous renvoyez une liste ordinaire, elle peut être modifiée. Utilisez toUnmodifiableList/Set/Map ou collectingAndThen pour « geler » le résultat.

Erreur n° 2 : mauvais collecteur downstream. S’il faut transformer les éléments à l’intérieur du groupe — utilisez mapping ; s’il faut filtrer — filtering ; s’il faut aplatir des collections imbriquées — flatMapping.

Erreur n° 3 : UnsupportedOperationException. Survient lors d’une tentative de modification d’une collection collectée via toUnmodifiableList/Set/Map ou « gelée » via collectingAndThen.

Erreur n° 4 : perte d’unicité/collisions de clés. toUnmodifiableSet exige des éléments uniques, et toUnmodifiableMap — des clés uniques ; sinon vous obtiendrez une exception lors de la collecte.

1
Mission
JAVA 25 SELF, niveau 31, leçon 4
Bloqué
Tri des candidats à l'Académie des Grandes Réussites 📜
Tri des candidats à l'Académie des Grandes Réussites 📜
1
Mission
JAVA 25 SELF, niveau 31, leçon 4
Bloqué
Analyse express du portefeuille financier 💰
Analyse express du portefeuille financier 💰
1
Étude/Quiz
Regroupement et agrégation, niveau 31, leçon 4
Indisponible
Regroupement et agrégation
Stream API: regroupement et agrégation
Commentaires
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION