1. Stream.concat: fusionner deux flux
En programmation, on rencontre souvent la situation suivante: nous avons deux (ou plus) flux de données et nous voulons les fusionner en un seul. Par exemple, deux listes d’étudiants de groupes différents — et il faut tous les traiter d’un coup. À l’époque (avant le Stream API), nous aurions simplement fusionné deux listes avec addAll. Mais si nous travaillons avec des flux (Stream<T>), nous voulons le faire de manière paresseuse et expressive.
Syntaxe et principe de fonctionnement
La manière la plus basique de fusionner deux flux est d’utiliser la méthode statique Stream.concat:
Stream<T> Stream.concat(Stream<? extends T> a, Stream<? extends T> b)
Ce qui se passe:
- On prend d’abord le premier flux, puis le second.
- En sortie — un nouveau flux qui émet d’abord tous les éléments du premier, puis tous ceux du second.
- La fusion est paresseuse: tant que vous ne parcourez pas le flux final avec une opération terminale (par exemple, forEach ou collect), rien ne se passe.
Exemple: fusion de deux listes de prénoms
import java.util.List;
import java.util.stream.Stream;
List<String> groupA = List.of("Anna", "Boris", "Victoria");
List<String> groupB = List.of("George", "Daria");
Stream<String> allStudents = Stream.concat(groupA.stream(), groupB.stream());
allStudents.forEach(System.out::println);
Résultat:
Anna
Boris
Victoria
George
Daria
Important: après la fusion, le flux résultant est à usage unique. Comme tout autre Stream, il ne peut pas être réutilisé.
Particularités de Stream.concat
- Uniquement deux flux à la fois. Pour trois et plus — soit en chaînant concat, soit avec d’autres approches (voir ci-dessous).
- L’ordre est conservé. D’abord les éléments du premier flux, puis ceux du second.
- Fusion paresseuse. Si le premier flux est infini, on n’atteindra jamais le second.
- Erreur typique: essayer de fusionner un flux avec lui-même (Stream.concat(stream, stream)) revient à réutiliser le même flux — c’est interdit.
2. Fusion de plus de deux flux: flatMap et Stream.of
Quand il y a plus de deux flux, il est plus pratique de les rassembler grâce à la combinaison Stream.of + flatMap.
Utiliser Stream.of + flatMap
import java.util.Collection;
import java.util.List;
import java.util.stream.Stream;
List<String> groupA = List.of("Anna", "Boris");
List<String> groupB = List.of("Victoria");
List<String> groupC = List.of("George", "Daria");
Stream<String> allStudents = Stream.of(groupA, groupB, groupC)
.flatMap(Collection::stream);
allStudents.forEach(System.out::println);
Ce qui se passe ici:
- Stream.of(groupA, groupB, groupC) crée un flux de trois collections.
- flatMap(Collection::stream) déplie chaque collection en un flux unique de chaînes.
- Au final — un grand flux de tous les étudiants.
Avantage: vous pouvez fusionner autant de flux que nécessaire.
Autre variante: fusionner une liste de flux
import java.util.List;
import java.util.stream.Stream;
List<Stream<String>> streams = List.of(
Stream.of("a", "b"),
Stream.of("c"),
Stream.of("d", "e")
);
Stream<String> merged = streams.stream()
.flatMap(s -> s);
merged.forEach(System.out::println);
Résultat:
a
b
c
d
e
3. Collectors.joining: concaténer des chaînes avec un séparateur
Parfois, il ne faut pas seulement fusionner des flux, mais coller tous les éléments en une seule chaîne — par exemple, pour afficher des prénoms séparés par des virgules. Pour cela, il existe le collecteur Collectors.joining.
Syntaxe
String result = stream.collect(Collectors.joining(", "));
- Sans arguments: concatène les chaînes sans séparateur.
- Avec séparateur: place la chaîne indiquée entre les éléments.
- Avec préfixe et suffixe: Collectors.joining(delimiter, prefix, suffix)
Exemple: liste des étudiants en une seule chaîne
import java.util.List;
import java.util.stream.Collectors;
List<String> students = List.of("Anna", "Boris", "Victoria");
String line = students.stream()
.collect(Collectors.joining(", "));
System.out.println("Liste des étudiants: " + line);
Résultat:
Liste des étudiants: Anna, Boris, Victoria
Exemple avec préfixe et suffixe
String line = students.stream()
.collect(Collectors.joining(", ", "[", "]"));
System.out.println(line);
Résultat:
[Anna, Boris, Victoria]
À quoi ça sert ?
- Génération de lignes CSV.
- KBel affichage de listes et de rapports.
- Transmission de données sous forme d’une seule chaîne (par exemple, dans une URL ou un log).
4. Comparaison de concat et flatMap: quand utiliser quoi ?
- Stream.concat — quand il y a exactement deux flux et qu’il est important de conserver l’ordre.
- Stream.of + flatMap — quand il y a beaucoup de flux ou qu’ils sont stockés dans une collection.
- Collectors.joining — quand les éléments sont de type String et que vous avez besoin d’un résultat sous forme d’une seule chaîne.
Tableau comparatif
| Méthode | Quand l’utiliser | Exemple de code |
|---|---|---|
|
Deux flux | |
|
De nombreux flux, collection de flux | |
|
Assembler les éléments en une seule chaîne | |
5. Exemples pratiques dans une application pédagogique
Exemple 1. Fusionner des étudiants de deux facultés
import java.util.List;
import java.util.stream.Stream;
List<String> itStudents = List.of("Anna", "Boris");
List<String> mathStudents = List.of("Victoria", "George");
Stream<String> all = Stream.concat(itStudents.stream(), mathStudents.stream());
all.forEach(System.out::println);
Exemple 2. Fusionner des étudiants de tous les groupes, afficher sur une seule ligne
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
List<List<String>> allGroups = List.of(
List.of("Anna", "Boris"),
List.of("Victoria"),
List.of("George", "Daria")
);
String allNames = allGroups.stream()
.flatMap(Collection::stream)
.collect(Collectors.joining("; "));
System.out.println("Tous les étudiants: " + allNames);
Résultat:
Tous les étudiants: Anna; Boris; Victoria; George; Daria
Exemple 3. Fusionner des flux de nombres
import java.util.stream.Stream;
Stream<Integer> s1 = Stream.of(1, 2, 3);
Stream<Integer> s2 = Stream.of(4, 5);
Stream<Integer> merged = Stream.concat(s1, s2);
merged.forEach(System.out::print); // 12345
6. Points importants et particularités
concat ne fonctionne qu’avec deux flux
Pour trois flux, on obtient un enchaînement imbriqué — cela fonctionne, mais c’est verbeux. Il vaut mieux utiliser Stream.of + flatMap:
Stream<Integer> s1 = Stream.of(1);
Stream<Integer> s2 = Stream.of(2);
Stream<Integer> s3 = Stream.of(3);
Stream<Integer> merged = Stream.concat(Stream.concat(s1, s2), s3);
Les flux sont à usage unique
Après une opération terminale, un flux ne peut plus être utilisé. Une réutilisation entraîne une IllegalStateException.
Le flux concat est paresseux
Si le premier flux est infini (par exemple, créé via Stream.generate(...)), on n’atteindra jamais le second.
Ordre des éléments
L’ordre est toujours conservé: d’abord le premier flux, puis le second.
Collectors.joining ne fonctionne qu’avec Stream<String>
Pour des types non chaînes, convertissez d’abord les éléments en chaînes, par exemple via map(Object::toString):
import java.util.stream.Collectors;
import java.util.stream.Stream;
Stream<Integer> numbers = Stream.of(1, 2, 3);
String line = numbers
.map(Object::toString)
.collect(Collectors.joining(", "));
System.out.println(line); // 1, 2, 3
Comparaison avec l’approche « manuelle »
Avant le Stream API:
import java.util.ArrayList;
import java.util.List;
List<String> merged = new ArrayList<>(listA);
merged.addAll(listB);
Avec le Stream API:
import java.util.stream.Collectors;
List<String> merged = Stream.concat(listA.stream(), listB.stream())
.collect(Collectors.toList());
Avantages des streams: vous pouvez insérer des opérations intermédiaires (filter, map, etc.) avant et après la fusion, et travailler avec n’importe quelle source de données.
7. Erreurs courantes lors de la fusion de flux
Erreur n° 1: réutilisation d’un flux. Un flux ne peut être utilisé qu’une seule fois. Il est interdit de fusionner un flux avec lui-même.
import java.util.stream.Stream;
Stream<String> s = Stream.of("a", "b");
Stream<String> merged = Stream.concat(s, s); // Erreur ! s sera réutilisé
Erreur n° 2: tentative de fusionner un flux infini avec un flux fini. Si le premier flux est infini, le second ne commencera jamais.
import java.util.stream.Stream;
Stream<Integer> infinite = Stream.generate(() -> 1);
Stream<Integer> finite = Stream.of(2, 3);
Stream<Integer> merged = Stream.concat(infinite, finite);
// merged.limit(10).forEach(System.out::println); // finite ne figurera pas dans le résultat
Erreur n° 3: joining pour des non-chaînes. Le collecteur Collectors.joining attend un flux de chaînes. Pour des nombres, convertissez d’abord en chaînes:
import java.util.List;
import java.util.stream.Collectors;
List<Integer> numbers = List.of(1, 2, 3);
// numbers.stream().collect(Collectors.joining(", ")); // Erreur de compilation !
String joined = numbers.stream()
.map(Object::toString)
.collect(Collectors.joining(", "));
Erreur n° 4: perturbation de l’ordre. Si l’ordre est important, utilisez concat ou un flatMap ordonné. Des méthodes comme unordered() peuvent rompre l’ordre déterministe de sortie.
GO TO FULL VERSION