CodeGym /Cours /JAVA 25 SELF /Analyse des erreurs courantes lors de l’utilisation des f...

Analyse des erreurs courantes lors de l’utilisation des flux

JAVA 25 SELF
Niveau 32 , Leçon 4
Disponible

1. Erreur : réutilisation du flux

Un flux (Stream) en Java est un outil à usage unique. Dès que vous exécutez une opération terminale (par exemple collect(), forEach(), count()), le flux est fermé et devient inutilisable par la suite.

Exemple d’erreur

Stream<String> stream = Stream.of("a", "b", "c");
stream.forEach(System.out::println); // Tout va bien
stream.forEach(System.out::println); // Provoquera une IllegalStateException!

Sortie :

a
b
c
Exception in thread "main" java.lang.IllegalStateException: stream has already been operated upon or closed

Comment l’éviter

Si vous devez effectuer plusieurs opérations — créez un nouveau flux à chaque fois :

List<String> list = List.of("a", "b", "c");
list.stream().forEach(System.out::println); // OK
list.stream().count(); // OK

Astuce : Un flux, c’est comme un gobelet jetable à la cantine : tu manges — tu le jettes, et tu en prends un nouveau pour la boisson suivante.

2. Erreur : modification de la collection pendant le stream

Si vous modifiez la collection source pendant l’itération via un flux, vous obtiendrez une ConcurrentModificationException. C’est une douleur fréquente lors du filtrage et de la suppression d’éléments.

Exemple d’erreur

List<String> names = new ArrayList<>(List.of("Alice", "Bob", "Charlie"));
names.stream().forEach(name -> {
    if (name.startsWith("A")) {
        names.remove(name); // BOUM !
    }
});

Résultat :

Exception in thread "main" java.util.ConcurrentModificationException

Comment faire correctement

Utilisez des méthodes qui ne modifient pas la collection pendant l’itération, ou collectez le résultat dans une nouvelle collection :

names.removeIf(name -> name.startsWith("A")); // OK, removeIf est sûr

// Ou avec l’API Stream :
List<String> filtered = names.stream()
    .filter(name -> !name.startsWith("A"))
    .collect(Collectors.toList());

À retenir : Les flux aiment la propreté — ne touchez pas à la collection source tant qu’ils la « consomment ».

3. Erreur : limit oublié avec les flux infinis

Les méthodes Stream.iterate et Stream.generate peuvent créer des flux infinis. Si vous oubliez de les limiter avec limit, le programme peut tourner indéfiniment (ou jusqu’à un OutOfMemoryError).

Exemple d’erreur

Stream<Integer> infinite = Stream.iterate(0, n -> n + 1);
infinite.forEach(System.out::println); // Le programme ne se terminera jamais !

Comment faire correctement

Limitez le flux avec limit :

Stream<Integer> finite = Stream.iterate(0, n -> n + 1).limit(10);
finite.forEach(System.out::println); // Affichera les nombres de 0 à 9

Conseil : Les flux infinis ressemblent à une série sans épisode final : quelqu’un doit l’arrêter, et autant que ce soit vous !

4. Erreur : mauvaise gestion de null

L’API Stream n’aime pas null. Si un null se glisse dans une collection, de nombreuses opérations peuvent lancer une NullPointerException, surtout si vous appelez des méthodes sur les éléments.

Exemple d’erreur

List<String> names = Arrays.asList("Alice", null, "Bob");
names.stream()
    .map(String::toUpperCase) // BOUM ! Sur null
    .forEach(System.out::println);

Résultat :

Exception in thread "main" java.lang.NullPointerException

Comment faire correctement

Filtrez les valeurs null en amont :

names.stream()
    .filter(Objects::nonNull)
    .map(String::toUpperCase)
    .forEach(System.out::println);

Utilisation d’Optional

Si vous avez un Stream<Optional<T>>, utilisez flatMap(Optional::stream) (Java 9+) pour « déplier » les valeurs :

List<Optional<String>> optionals = List.of(Optional.of("A"), Optional.empty(), Optional.of("B"));
optionals.stream()
    .flatMap(Optional::stream)
    .forEach(System.out::println); // Affichera uniquement "A" et "B"

Conseil : Évitez d’ajouter des null dans les collections. Si vous n’avez vraiment pas le choix, filtrez-les dès le départ.

5. Erreur : perte de l’ordre des éléments

Certaines opérations (par exemple flatMap, concat) peuvent affecter l’ordre des éléments, en particulier si vous travaillez avec des flux parallèles ou que vous fusionnez différentes collections.

Exemple d’erreur

List<Integer> list1 = List.of(1, 2, 3);
List<Integer> list2 = List.of(4, 5, 6);
Stream<Integer> combined = Stream.concat(list1.stream(), list2.stream());
combined.forEach(System.out::print); // 123456 — l’ordre est conservé

// Mais avec des flux parallèles :
Stream<Integer> par = Stream.concat(list1.parallelStream(), list2.parallelStream());
par.forEach(System.out::print); // L’ordre peut être inattendu !

Comment faire correctement

Si l’ordre est important — évitez les flux parallèles ou utilisez des méthodes qui garantissent l’ordre (forEachOrdered) :

par.forEachOrdered(System.out::print);

À savoir : Par défaut, l’API Stream conserve l’ordre, sauf si vous l’avez vous‑même « cassé ».

6. Erreur : fusion inefficace de collections

Pour les opérations sur des ensembles, utilisez plutôt Set que List, afin d’éviter les doublons et d’améliorer les performances. Il est également inefficace d’écrire des boucles imbriquées là où flatMap suffit.

Exemple d’erreur

List<String> list1 = List.of("a", "b", "c");
List<String> list2 = List.of("b", "c", "d");

// Inefficace :
List<String> union = new ArrayList<>(list1);
for (String s : list2) {
    if (!union.contains(s)) { // contains sur List — O(n)
        union.add(s);
    }
}

// Efficace avec Set et l’API Stream :
Set<String> unionSet = Stream.concat(list1.stream(), list2.stream())
    .collect(Collectors.toSet());

Ou pour la différence :

// Inefficace :
List<String> diff = new ArrayList<>(list1);
diff.removeAll(list2); // OK, mais si vous le faisiez avec stream et list.contains — ce serait lent

// Efficace :
Set<String> set2 = new HashSet<>(list2);
List<String> difference = list1.stream()
    .filter(s -> !set2.contains(s))
    .collect(Collectors.toList());

Conseil : Pour travailler avec des éléments uniques, utilisez toujours un Set. Pour de grandes collections — évitez les boucles imbriquées, privilégiez stream + flatMap.

7. Erreur : réutilisation d’opérations terminales

Parfois, on veut « scinder » un flux en deux branches, par exemple collecter les éléments dans une liste et en parallèle compter leur nombre. Mais un flux est « à usage unique », et la seconde opération provoquera une erreur.

Exemple d’erreur

Stream<String> stream = Stream.of("a", "b", "c");
List<String> list = stream.collect(Collectors.toList());
long count = stream.count(); // BOUM ! IllegalStateException

Comment faire correctement

Si vous devez utiliser un flux plusieurs fois — collectez-le d’abord dans une collection :

List<String> list = Stream.of("a", "b", "c").collect(Collectors.toList());
long count = list.size();

Conseil : Si vous voulez le beurre et l’argent du beurre — collectez d’abord dans une collection.

8. Erreur : close() oublié sur les ressources

Si vous utilisez des flux liés à des ressources (par exemple Files.lines(Path)), n’oubliez pas de les fermer après utilisation pour éviter les fuites.

Exemple d’erreur

Stream<String> lines = Files.lines(Path.of("file.txt"));
lines.forEach(System.out::println); // OK, mais la ressource n’est pas fermée !

Comment faire correctement

Utilisez try-with-resources :

try (Stream<String> lines = Files.lines(Path.of("file.txt"))) {
    lines.forEach(System.out::println);
}

Conseil : Les flux de fichiers sont comme une bouilloire : tu la fais bouillir — tu l’éteins !

9. Erreur : gestion incorrecte d’Optional et de null dans flatMap

En travaillant avec des collections de type List<Optional<T>>, beaucoup utilisent map(Optional::get), ce qui mène à des exceptions si l’Optional est vide.

Exemple d’erreur

List<Optional<String>> optionals = List.of(Optional.of("A"), Optional.empty());
optionals.stream()
    .map(Optional::get) // BOUM ! NoSuchElementException
    .forEach(System.out::println);

Comment faire correctement

Utilisez flatMap(Optional::stream) (Java 9+) :

optionals.stream()
    .flatMap(Optional::stream)
    .forEach(System.out::println); // Seulement "A"

Conseil : Optional n’est pas « juste un wrapper », c’est un outil pour travailler en toute sécurité avec l’absence de valeur.

10. Erreur : equals/hashCode mal implémentés

Les opérations sur les ensembles (Set, distinct, union, intersection) dépendent d’une implémentation correcte de equals et hashCode.

Exemple d’erreur

class Student {
    String name;
    int age;
    // Pas de equals ni de hashCode !
}

Set<Student> students = new HashSet<>();
students.add(new Student("Alice", 20));
students.add(new Student("Alice", 20));
System.out.println(students.size()); // 2, alors qu’ils devraient être identiques !

Comment faire correctement

Redéfinissez equals et hashCode dans vos classes si vous prévoyez de les utiliser dans des Set, des Map ou pour des opérations distinct.

11. Erreur : utilisation du stream après collect

Après une opération terminale (par exemple collect), le flux est fermé. Toute tentative de continuer avec ce même flux conduira à une erreur.

Exemple d’erreur

Stream<String> stream = Stream.of("a", "b", "c");
List<String> list = stream.collect(Collectors.toList());
stream.filter(s -> s.startsWith("a")).forEach(System.out::println); // Exception !

Comment faire correctement

Créez un nouveau flux à partir de la collection :

list.stream().filter(s -> s.startsWith("a")).forEach(System.out::println);

12. Erreur : mauvaise utilisation de parallelStream

Beaucoup pensent que parallelStream() accélère toujours le traitement. En réalité, pour de petites collections ou des opérations simples, le parallélisme peut même ralentir l’exécution à cause des surcoûts de gestion des threads.

Exemple d’erreur

List<Integer> numbers = IntStream.range(0, 10).boxed().collect(Collectors.toList());
numbers.parallelStream().forEach(System.out::println); // Peut être plus lent que stream() classique

Comment faire correctement

Utilisez parallelStream seulement pour de très grandes collections ou des calculs lourds, surtout si les opérations sont pures (sans effets de bord) et associatives.

13. Erreur : opérateur terminal manquant

Les opérations intermédiaires (map, filter, flatMap, etc.) ne s’exécutent pas tant qu’aucun opérateur terminal (collect, forEach, count, etc.) n’a été appelé.

Exemple d’erreur

Stream<String> stream = Stream.of("a", "b", "c").map(String::toUpperCase);
// Il ne se passera rien !

Comment faire correctement

Appelez impérativement un opérateur terminal :

stream.forEach(System.out::println);
1
Étude/Quiz
Unions et projections, niveau 32, leçon 4
Indisponible
Unions et projections
Stream API: unions et projections
Commentaires
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION