CodeGym /Cours /JAVA 25 SELF /Opérations de base de l’API Stream: map, filter, collect

Opérations de base de l’API Stream: map, filter, collect

JAVA 25 SELF
Niveau 30 , Leçon 1
Disponible

1. Créer un flux

Pour utiliser l’API Stream, il faut d’abord obtenir un flux à partir d’une collection ou d’un tableau.

Exemples de création d’un flux

// À partir d’une liste
List<String> names = List.of("Anna", "Boris", "Alex", "Alina");
Stream<String> stream = names.stream();

// À partir d’un tableau
int[] numbers = {1, 2, 3, 4, 5};
IntStream intStream = Arrays.stream(numbers);

// À partir de valeurs individuelles
Stream<String> letters = Stream.of("A", "B", "C");

En bref :

  • list.stream() — pour les collections
  • Arrays.stream(array) — pour les tableaux
  • Stream.of(...) — pour des valeurs individuelles

Exemple dans le contexte de notre application

Supposons que nous ayons une liste d’utilisateurs :

List<String> users = List.of("Ivan", "Anna", "Petr", "Alexey");
Stream<String> userStream = users.stream();

Opérations intermédiaires et terminales

Point important : les opérations dans l’API Stream se divisent en deux types.

  • Opérations intermédiaires (par exemple, filter, map, distinct) — elles décrivent des étapes de traitement. Elles renvoient un nouveau flux, mais ne déclenchent rien par elles‑mêmes.
  • Opérations terminales (par exemple, collect, forEach, count) — elles déclenchent le pipeline et produisent un résultat.

Un flux fonctionne « paresseusement » : tant qu’aucune opération terminale n’est appelée, aucun calcul n’a lieu. C’est pourquoi on termine souvent la chaîne par collect(...) — c’est le point où le flux redevient une collection ou un autre résultat.

2. Opération filter: filtrer des éléments selon une condition

filter est une opération intermédiaire qui ne laisse passer que les éléments correspondant à une condition donnée.

Signature

Stream<T> filter(Predicate<? super T> predicate); 

Predicate est une interface fonctionnelle qui prend un élément et renvoie true (garder) ou false (écarter).

Exemple : ne garder que les nombres pairs

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

List<Integer> evenNumbers = numbers.stream()
    .filter(n -> n % 2 == 0)
    .collect(Collectors.toList());

System.out.println(evenNumbers); // [2, 4, 6]

Que se passe‑t‑il ?

  • n -> n % 2 == 0 — une expression lambda qui vérifie si le nombre est divisible par 2 sans reste.
  • filter ne conserve que les nombres pairs.

Exemple : filtrer les prénoms commençant par « A »

List<String> names = List.of("Anna", "Boris", "Alex", "Alina", "Ivan");

List<String> aNames = names.stream()
    .filter(name -> name.startsWith("A"))
    .collect(Collectors.toList());

System.out.println(aNames); // [Anna, Alex, Alina]

Point important : filter ne modifie pas la collection — il crée un nouveau flux qui ne contient que les éléments requis.

3. Opération map: transformer un élément en autre chose

map est une opération de transformation. Elle prend chaque élément du flux, applique une fonction et renvoie un nouvel élément.

Signature

<R> Stream<R> map(Function<? super T, ? extends R> mapper)

Function est une interface qui prend un élément et renvoie quelque chose (éventuellement d’un autre type).

Exemple : obtenir la longueur des chaînes

List<String> names = List.of("Anna", "Boris", "Alex");

List<Integer> nameLengths = names.stream()
    .map(name -> name.length())
    .collect(Collectors.toList());

System.out.println(nameLengths); // [4, 5, 4]

Que se passe‑t‑il ?

  • map transforme une chaîne en sa longueur (name -> name.length()).
  • On obtient au final un flux de nombres.

Exemple : passer les chaînes en majuscules

List<String> names = List.of("Anna", "Boris", "Alex");

List<String> upperNames = names.stream()
    .map(name -> name.toUpperCase())
    .collect(Collectors.toList());

System.out.println(upperNames); // [ANNA, BORIS, ALEX]

4. Opération collect: rassembler le résultat dans une collection

collect est une opération terminale, c’est‑à‑dire qu’elle achève le travail du flux et rassemble le résultat dans une collection ou un autre conteneur.

Signature

<R, A> R collect(Collector<? super T, A, R> collector)

Pas d’inquiétude face à cette signature impressionnante ! Dans 99 % des cas, vous utiliserez des collecteurs prêts à l’emploi de la classe Collectors.

Collectors est une classe utilitaire proposant un ensemble de « collecteurs ». Elle indique au flux sous quelle forme rassembler le résultat : liste, ensemble, chaîne, etc.

Exemples :

  • Collectors.toList() — vers une List
  • Collectors.toSet() — vers un Set
  • Collectors.joining(", ") — en une chaîne séparée par des virgules

Autrement dit, Collectors est comme un jeu de boîtes de formes différentes dans lesquelles vous emballez les éléments du flux.

Exemple : rassembler le résultat dans une List

List<String> filtered = names.stream()
    .filter(name -> name.length() > 3)
    .collect(Collectors.toList());

Exemple : rassembler le résultat dans un Set

Set<String> uniqueNames = names.stream()
    .map(String::toLowerCase)
    .collect(Collectors.toSet());

Exemple : rassembler les chaînes séparées par des virgules

String result = names.stream()
    .collect(Collectors.joining(", "));

System.out.println(result); // Anna, Boris, Alex

5. Chaînage d’opérations: filtrage + transformation + collecte du résultat

La plus grande force de l’API Stream — c’est la possibilité d’enchaîner les opérations les unes après les autres.

Exemple : obtenir la longueur des prénoms commençant par « A »

List<String> names = List.of("Anna", "Boris", "Alex", "Alina", "Ivan");

List<Integer> aNameLengths = names.stream()
    .filter(name -> name.startsWith("A"))
    .map(String::length)
    .collect(Collectors.toList());

System.out.println(aNameLengths); // [4, 4, 5]

Étape par étape :

  1. .stream() — créer un flux à partir de la liste.
  2. .filter(name -> name.startsWith("A")) — ne garder que les prénoms commençant par "A".
  3. .map(String::length) — transformer chaque prénom en sa longueur.
  4. .collect(Collectors.toList()) — rassembler le résultat dans une liste.

Code impératif équivalent

Voici à quoi ressemblerait la même chose « à l’ancienne » :

List<Integer> result = new ArrayList<>();
for (String name : names) {
    if (name.startsWith("A")) {
        result.add(name.length());
    }
}

Comparez : avec l’API Stream — une seule ligne, on lit « ce que l’on fait », et non « comment on le fait ».

6. Pratique: quelques exercices courts

Entraînons‑nous ! Tous les exemples peuvent être exécutés dans un seul fichier — il suffit de changer les données.

Exercice 1: ne garder que les nombres impairs et les mettre au carré

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

List<Integer> oddSquares = numbers.stream()
    .filter(n -> n % 2 != 0)
    .map(n -> n * n)
    .collect(Collectors.toList());

System.out.println(oddSquares); // [1, 9, 25, 49]

Exercice 2: à partir d’une liste de chaînes, obtenir la liste de leurs premières lettres

List<String> names = List.of("Anna", "Boris", "Alex");

List<Character> initials = names.stream()
    .map(name -> name.charAt(0))
    .collect(Collectors.toList());

System.out.println(initials); // [A, B, A]

Exercice 3: filtrer les chaînes de longueur supérieure à 3 et les rassembler dans un Set

List<String> words = List.of("cat", "dog", "elephant", "ant", "bear");

Set<String> longWords = words.stream()
    .filter(word -> word.length() > 3)
    .collect(Collectors.toSet());

System.out.println(longWords); // [bear, elephant]

7. Erreurs typiques lors de l’utilisation de filter, map, collect

Erreur n° 1 : vous avez oublié collect — aucun résultat !
L’API Stream est paresseuse comme un chat sur le rebord de la fenêtre : tant que vous n’appelez pas une opération terminale (par exemple, collect ou forEach), il ne se passera rien. Si vous écrivez seulement users.stream().filter(...).map(...); — rien ne sera exécuté.

Erreur n° 2 : filter et map inversés
Parfois, les débutants font d’abord map, puis filter. Par exemple, names.stream().map(String::length).filter(len -> len > 3) produira des nombres, pas des chaînes. Si vous avez besoin des chaînes de longueur supérieure à 3, filtrez d’abord, puis transformez.

Erreur n° 3 : oublier l’immuabilité
Les opérations de l’API Stream ne modifient pas la collection d’origine ! Elles renvoient un nouveau résultat. Après List<String> upper = names.stream().map(String::toUpperCase).collect(Collectors.toList()); — la collection names restera inchangée.

Erreur n° 4 : tenter d’utiliser une liste externe modifiable
À éviter :

List<String> result = new ArrayList<>();
names.stream().filter(...).forEach(name -> result.add(name));

Mieux vaut utiliser collect — c’est plus sûr et plus court.

Erreur n° 5 : NullPointerException
Si la collection peut contenir des éléments null, l’appel de name.startsWith("A") sur null provoquera une erreur. Ajoutez un filtre sur null lorsque c’est possible :

.filter(name -> name != null && name.startsWith("A"))
Commentaires
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION