CodeGym /Cours /JAVA 25 SELF /Méthodes reduce et collect: agrégation des données

Méthodes reduce et collect: agrégation des données

JAVA 25 SELF
Niveau 31 , Leçon 1
Disponible

1. Méthode reduce: réduction universelle

En programmation, il faut souvent « réduire » une collection à une valeur finale unique: calculer une somme, trouver un produit, concaténer des chaînes, calculer une métrique agrégée ou rassembler des éléments dans une nouvelle structure. Autrefois, on le faisait manuellement via des boucles et une variable accumulateur. Aujourd’hui, l’API Stream propose des moyens élégants — les méthodes universelles reduce et collect, qui permettent d’écrire du code compact et déclaratif.

  • reduce — réduit un flux à une valeur finale unique (somme, produit, concaténation, etc.).
  • collect — transforme un flux en collection, chaîne, map ou structure arbitraire.

Voyons cela pas à pas.

À quoi sert reduce ?

reduce — une méthode terminale qui « réduit » les éléments d’un flux à une seule valeur à l’aide d’une fonction d’accumulation. Conceptuellement, cela ressemble à un parcours de la collection avec une accumulation pas à pas du résultat.

Signatures de la méthode reduce

Dans l’API Stream, il existe trois variantes principales de reduce():

Optional<T> reduce(BinaryOperator<T> accumulator)
T reduce(T identity, BinaryOperator<T> accumulator)
<U> U reduce(U identity, BiFunction<U, ? super T, U> accumulator, BinaryOperator<U> combiner)
  • accumulator — fonction prenant la valeur accumulée courante et l’élément suivant, et renvoyant un nouveau résultat.
  • identity — valeur initiale de l’accumulateur (par exemple, 0 pour la somme, 1 pour le produit).
  • combiner — utilisé dans les flux parallèles pour fusionner des résultats intermédiaires.

Exemples d’utilisation de reduce

Exemple 1: Somme des nombres

List<Integer> numbers = List.of(1, 2, 3, 4, 5);

// reduce sans identity — le résultat est un Optional
Optional<Integer> sum1 = numbers.stream()
    .reduce((a, b) -> a + b);

System.out.println(sum1.orElse(0)); // 15

// reduce avec identity — le résultat existe toujours
int sum2 = numbers.stream()
    .reduce(0, (a, b) -> a + b);

System.out.println(sum2); // 15

Exemple 2: Produit de tous les nombres

int product = numbers.stream()
    .reduce(1, (a, b) -> a * b);
System.out.println(product); // 120

Exemple 3: Concaténation de chaînes

List<String> words = List.of("Java", "Stream", "API");
String phrase = words.stream()
    .reduce("", (a, b) -> a + " " + b);
System.out.println(phrase.trim()); // Java Stream API

Exemple 4: Recherche de l’élément maximum

Optional<Integer> max = numbers.stream()
    .reduce(Integer::max);
max.ifPresent(System.out::println); // 5

Exemple 5: Somme des longueurs de toutes les chaînes

List<String> texts = List.of("chat", "chien", "éléphant");
int totalLength = texts.stream()
    .map(String::length)
    .reduce(0, Integer::sum);
System.out.println(totalLength); // 14

Comment fonctionne reduce

La logique de reduce est équivalente à la boucle suivante:

T result = identity;
for (T element : collection) {
    result = accumulator.apply(result, element);
}
return result;

Si identity n’est pas fourni, la valeur de départ est le premier élément du flux, et la méthode renvoie un Optional (qui peut être vide si le flux est vide).

2. Méthode collect: transformation universelle

collect — une méthode terminale qui transforme un flux en collection, chaîne, map ou toute autre structure. Pour cela, on utilise des « collecteurs » (Collector) qui décrivent le processus d’assemblage. Le plus souvent, on utilise ceux prêts à l’emploi de la classe Collectors.

Collecteurs les plus populaires

  • Collectors.toList() — rassemble les éléments dans une List.
  • Collectors.toSet() — rassemble les éléments dans un Set.
  • Collectors.toMap() — rassemble les éléments dans une Map.
  • Collectors.joining() — concatène des chaînes en une seule.
  • Collectors.groupingBy() — regroupe les éléments selon un critère.
  • Collectors.counting() — compte le nombre d’éléments.
  • Collectors.summarizingInt() — collecte des statistiques sur les nombres (somme, moyenne, min/max).

Exemples d’utilisation de collect

Exemple 1: Collecte dans une liste

List<String> names = List.of("Anya", "Boris", "Vasya", "Anya");
List<String> uniqueNames = names.stream()
    .distinct()
    .collect(Collectors.toList());
System.out.println(uniqueNames); // [Anya, Boris, Vasya]

Exemple 2: Collecte dans un ensemble (Set)

Set<String> nameSet = names.stream()
    .collect(Collectors.toSet());
System.out.println(nameSet); // [Anya, Boris, Vasya] (l'ordre n'est pas garanti)

Exemple 3: Collecte dans une chaîne

String csv = names.stream()
    .collect(Collectors.joining(", "));
System.out.println(csv); // Anya, Boris, Vasya, Anya

Exemple 4: Collecte dans une Map

Supposons que nous ayons une classe :

public class Employee {
    private String name;
    private String department;
    public Employee(String name, String department) {
        this.name = name;
        this.department = department;
    }
    public String getName() { return name; }
    public String getDepartment() { return department; }
}

Construisons une map « nom → département » :

List<Employee> employees = List.of(
    new Employee("Anya", "IT"),
    new Employee("Boris", "HR"),
    new Employee("Vasya", "IT")
);

Map<String, String> nameToDept = employees.stream()
    .collect(Collectors.toMap(
        Employee::getName,
        Employee::getDepartment,
        (oldValue, newValue) -> newValue // gestion des doublons de noms
    ));

System.out.println(nameToDept); // {Anya=IT, Boris=HR, Vasya=IT}

Exemple 5: Rassembler les éléments uniques dans un Set

Set<String> unique = names.stream()
    .collect(Collectors.toSet());
System.out.println(unique);

Exemple 6: Collecter des statistiques sur des nombres

List<Integer> numbers = List.of(1, 2, 3, 4, 5);
IntSummaryStatistics stats = numbers.stream()
    .collect(Collectors.summarizingInt(Integer::intValue));
System.out.println(stats.getSum());     // 15
System.out.println(stats.getAverage()); // 3.0
System.out.println(stats.getMax());     // 5
System.out.println(stats.getMin());     // 1

3. Comparaison: quand utiliser reduce et quand — collect

reduce — lorsque vous devez obtenir une valeur finale unique à l’aide d’une opération binaire: somme, produit, maximum, concaténation.

collect — lorsque vous devez rassembler des éléments dans une collection/map/chaîne ou effectuer une agrégation plus complexe avec un Collector. Pour ces tâches, collect est généralement plus puissant et plus efficace.

Tableau: reduce vs collect

Tâche À utiliser Exemple
Somme des nombres
reduce
reduce(0, Integer::sum)
Produit
reduce
reduce(1, (a, b) -> a * b)
Collecte dans List
collect
collect(Collectors.toList())
Collecte dans Map
collect
collect(Collectors.toMap(...))
Regroupement
collect
collect(Collectors.groupingBy(...))
Concaténation de chaînes reduce / collect reduce("", String::concat) ou Collectors.joining()

4. Exercices pratiques

Exercice 1: Trouver la somme des longueurs de toutes les chaînes de la liste

List<String> words = List.of("chat", "chien", "éléphant");

int totalLength = words.stream()
    .mapToInt(String::length)
    .sum(); // ou via reduce: .reduce(0, Integer::sum)

System.out.println(totalLength); // 14

Exercice 2: Rassembler les éléments uniques dans un Set

List<String> fruits = List.of("pomme", "poire", "pomme", "orange");

Set<String> uniqueFruits = fruits.stream()
    .collect(Collectors.toSet());

System.out.println(uniqueFruits); // [pomme, poire, orange]

Exercice 3: Construire une Map à partir d’une liste d’objets

List<Employee> employees = List.of(
    new Employee("Anya", "IT"),
    new Employee("Boris", "HR"),
    new Employee("Vasya", "IT")
);

Map<String, String> nameToDept = employees.stream()
    .collect(Collectors.toMap(
        Employee::getName,
        Employee::getDepartment,
        (oldValue, newValue) -> newValue // en cas de noms identiques
    ));

System.out.println(nameToDept);

Exercice 4: Rassembler tous les noms séparés par une virgule

String allNames = employees.stream()
    .map(Employee::getName)
    .collect(Collectors.joining(", "));

System.out.println(allNames); // Anya, Boris, Vasya

5. Particularités d’implémentation et subtilités

Optional et reduce

Si vous utilisez reduce sans identity, le résultat est un Optional. C’est sûr: si le flux est vide, le résultat l’est aussi. N’oubliez pas de le traiter correctement: ifPresent(...), orElse(...), orElseThrow(...).

Optional<Integer> max = numbers.stream().reduce(Integer::max);
max.ifPresent(System.out::println);

Collecteurs personnalisés: si vous voulez aller plus loin

Vous pouvez écrire votre propre Collector si les standard ne suffisent pas. Mais pour 99 % des cas, ceux de Collectors suffisent largement.

Collecteurs et streams parallèles

Les collecteurs prêts à l’emploi de Collectors sont conçus pour fonctionner correctement avec parallelStream(). N’ajoutez pas des éléments manuellement à une collection mutable partagée dans un forEach sur un flux parallèle — vous risquez des conditions de concurrence (data races).

6. Erreurs courantes avec reduce et collect

Erreur n° 1: Vous ne vérifiez pas Optional après reduce. Si le flux est vide, reduce sans identity renvoie un Optional vide. Appeler get() entraînera une NoSuchElementException. Utilisez ifPresent, orElse ou orElseThrow.

Erreur n° 2: Vous essayez de rassembler une collection via reduce. C’est possible, mais collect est mieux adapté et plus rapide pour cela:

// Inefficace !
List<String> list = stream.reduce(
    new ArrayList<>(),
    (acc, elem) -> { acc.add(elem); return acc; },
    (acc1, acc2) -> { acc1.addAll(acc2); return acc1; }
);
// Mieux ainsi :
List<String> list2 = stream.collect(Collectors.toList());

Erreur n° 3: Vous ne traitez pas les doublons de clés dans toMap. Si des clés en double apparaissent, une exception sera levée. Ajoutez le troisième argument à toMap pour résoudre les conflits.

Erreur n° 4: Vous utilisez des collections mutables dans des streams parallèles sans synchronisation. Dans collect, utilisez les collecteurs standard — ils fonctionnent correctement en mode parallèle. N’effectuez pas de list.add() dans un forEach sur un parallelStream().

Erreur n° 5: Vous confondez reduce et collect pour des tâches complexes. reduce — pour les agrégations simples (somme, maximum). collect — pour la collecte dans des collections, le regroupement, la construction de Map et l’agrégation complexe.

Commentaires
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION