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);
GO TO FULL VERSION