Créer des streams Java
Créez des streams Java depuis des collections, tableaux, Stream.of, Stream.iterate, Stream.generate et des sources I/O.
Le chapitre d'introduction a présenté la forme du pipeline — source → intermédiaires → terminal — en considérant la source comme acquise. Ce chapitre est le catalogue des sources. Chaque pipeline de stream que vous écrirez commence par l'une d'elles, et chacune possède quelques caractéristiques qui déterminent si le pipeline qui suit est correct, paresseux, fini, ordonné ou parallélisable.
La liste est plus courte qu'elle n'y paraît. Presque tous les streams que vous écrirez démarrent avec coll.stream(), Stream.of(...), Arrays.stream(arr) ou un IntStream.range. Le reste de ce chapitre est consacré aux rares situations où les autres sources sont le bon choix.
Depuis une Collection — coll.stream()
La source dominante. Collection<T> possède une méthode stream() par défaut, donc chaque List, Set, Queue et Deque l'expose gratuitement :
List<String> names = List.of("Alice", "Bob", "Carol");
long count = names.stream().filter(n -> n.length() > 3).count();Le stream est séquentiel, dimensionné (la JVM connaît le nombre d'éléments à l'avance), et ordonné si la collection l'est. Une List produit un stream ordonné ; un HashSet produit un stream non ordonné ; un TreeSet produit un stream ordonné selon le comparateur de l'ensemble.
Il existe aussi coll.parallelStream(), qui planifie l'exécution sur le ForkJoinPool commun. Même source, politique d'exécution différente — abordé dans Java Parallel Streams.
Depuis des éléments explicites — Stream.of(...)
Utilisez Stream.of quand vous avez une courte liste connue d'éléments et ne souhaitez pas créer une List temporaire :
Stream<String> s = Stream.of("a", "b", "c");
Stream<Integer> n = Stream.of(1, 2, 3, 4, 5);
Stream<Object> one = Stream.of("just one");C'est une méthode varargs, elle accepte donc n'importe quel nombre d'arguments (zéro est autorisé et produit un stream vide). Avec un seul argument T[], le compilateur choisit Stream.of(T...) et non Stream.of(T) — pratique quand vous avez déjà un tableau :
String[] arr = {"x", "y", "z"};
Stream<String> fromArr = Stream.of(arr); // same as Arrays.stream(arr)Depuis un tableau — Arrays.stream(...)
Arrays.stream possède des surcharges pour T[], int[], long[], double[], ainsi que des variantes avec plage :
int[] xs = {3, 1, 4, 1, 5, 9, 2, 6};
IntStream ix = Arrays.stream(xs); // primitive specialisation
IntStream tail = Arrays.stream(xs, 2, xs.length); // half-open [2, len)
String[] words = {"alpha", "beta", "gamma"};
Stream<String> ws = Arrays.stream(words);Les surcharges primitives retournent IntStream, LongStream, DoubleStream — et non Stream<Integer>. C'est important : les streams primitifs évitent le boxing, disposent de sum, average, min, max directement (sans collecteur), et s'articulent bien avec mapToInt/mapToObj pour passer d'un monde à l'autre.
Plages primitives — IntStream.range / rangeClosed
La façon la plus rapide d'itérer par index sans boucle for :
// 0, 1, 2, ..., 9
IntStream.range(0, 10).forEach(i -> System.out.println(i));
// 1..10 inclusive
int sum = IntStream.rangeClosed(1, 10).sum(); // 55range(a, b) est semi-ouvert [a, b). rangeClosed(a, b) est [a, b]. Les deux sont bornés, ordonnés, dimensionnés, et plus rapides que Stream.iterate(0, i -> i + 1).limit(n) car la JVM connaît le nombre à l'avance. Utilisez-les chaque fois que le corps d'une boucle consiste à « faire quelque chose à l'index i ».
Pour associer un index aux éléments d'une List, vous écrivez :
List<String> names = List.of("Alice", "Bob", "Carol");
IntStream.range(0, names.size())
.mapToObj(i -> i + ": " + names.get(i))
.forEach(System.out::println);Streams infinis générés — Stream.iterate et Stream.generate
Deux façons de produire un stream non borné. Elles se ressemblent ; elles ne sont pas équivalentes.
Stream.iterate(seed, f) — commence par seed, puis f(seed), puis f(f(seed)), …. Ordonné, déterministe, séquentiel. Presque toujours suivi d'un court-circuit :
Stream.iterate(1, n -> n * 2)
.limit(10)
.forEach(System.out::println); // 1, 2, 4, 8, ..., 512Il existe aussi une surcharge à 3 arguments Stream.iterate(seed, hasNext, next) (Java 9+) qui intègre la condition d'arrêt dans la source — pas besoin de limit :
Stream.iterate(1, n -> n < 1000, n -> n * 2).forEach(System.out::println);Stream.generate(supplier) — appelle un Supplier<T> de manière répétée. Non ordonné, aucune relation entre les éléments :
Stream.generate(Math::random).limit(5).forEach(System.out::println);
Stream.generate(() -> "ping").limit(3).forEach(System.out::println);Utilisez iterate pour les suites où chaque terme dépend du précédent (n -> n + 1, n -> n * 2, la paire Fibonacci arr -> {arr[1], arr[0] + arr[1]}). Utilisez generate pour des valeurs indépendantes provenant d'une source externe — nombres aléatoires, constantes fixes, UUID.
Dans les deux cas, terminez toujours avec une opération en court-circuit : limit(n), le iterate à 3 arguments, ou un terminal comme findFirst / anyMatch. Un simple toList() sur un stream infini bloque la JVM.
Depuis l'I/O — Files.lines, BufferedReader.lines
Files.lines(path) ouvre un fichier et retourne un Stream<String> de ses lignes. Paresseux : les lignes sont lues au fur et à mesure que le pipeline les demande, pas à l'avance :
try (Stream<String> lines = Files.lines(Path.of("words.txt"))) {
long longWords = lines.filter(w -> w.length() > 8).count();
}Le try-with-resources est obligatoire. Le stream maintient un descripteur de fichier ouvert, et la seule façon de le libérer est d'appeler close() — ce que fait try-with-resources pour vous. Sans cela, le descripteur fuit jusqu'à ce que le stream soit collecté par le garbage collector, ce qui peut ne jamais arriver sous charge.
Même forme pour les Reader via BufferedReader.lines(). Les deux sont la façon canonique de parcourir un fichier texte sans le charger en mémoire.
String.chars() et String.codePoints()
Un String est une séquence d'unités de code UTF-16 ; l'API expose les deux vues :
"hello".chars() // IntStream of UTF-16 code units
.filter(Character::isUpperCase)
.count();
"héllo".codePoints() // IntStream of Unicode code points
.mapToObj(Character::toString)
.forEach(System.out::println);Les deux retournent un IntStream. chars() convient pour l'ASCII ; pour tout ce qui pourrait contenir des paires de substitution (la plupart des emoji, de nombreuses écritures), codePoints() est le choix sûr.
Streams vides et à un seul élément
Pour les cas par défaut et les branches flatMap :
Stream<String> none = Stream.empty(); // 0 elements
Stream<String> one = Stream.of("x"); // exactly 1
Stream<String> opt = Optional.of("x").stream(); // 1 if present, else emptyOptional.stream() (Java 9+) est le pont entre Optional<T> et Stream<T> — pratique quand vous flatMap un stream d'Optional en un stream de valeurs présentes, sans aucune gestion de null.
Stream.Builder — pousser des éléments un par un
Quand vous ne pouvez pas exprimer la source comme un littéral, un tableau ou un générateur — généralement parce que les éléments proviennent de branches disparates de code impératif — il existe un builder :
Stream.Builder<String> b = Stream.builder();
b.add("first");
if (someCondition) b.add("second");
b.accept("third");
Stream<String> s = b.build();Après build(), le builder est scellé ; tout add ultérieur lève une exception. C'est un outil rare mais légitime. La plupart du code qui y recourt serait mieux écrit avec une ArrayList<String> suivie de list.stream(), mais le builder évite cette collection intermédiaire quand les données sont construites par morceaux.
Streams de Map — il n'en existe pas
Map<K, V> ne possède pas de méthode stream(). À la place, vous streamez ses vues :
Map<String, Integer> ages = Map.of("Alice", 30, "Bob", 25);
ages.entrySet().stream().filter(e -> e.getValue() >= 18).map(Map.Entry::getKey).toList();
ages.keySet().stream().sorted().toList();
ages.values().stream().mapToInt(Integer::intValue).sum();entrySet().stream() est ce dont vous aurez le plus souvent besoin — les deux moitiés de chaque entrée sont en portée, et Map.Entry::getKey / ::getValue fonctionnent comme des références de méthode.
Choisir la bonne source
| Situation | Utiliser |
|---|---|
Vous avez déjà une List, Set, Queue | coll.stream() |
| Vous avez quelques éléments fixes | Stream.of(a, b, c) |
Vous avez un T[] | Arrays.stream(arr) |
Vous avez int[], long[], double[] | Arrays.stream(arr) → stream primitif |
| Vous voulez itérer par index | IntStream.range(0, n) |
| Vous voulez chaque terme à partir du précédent | Stream.iterate |
| Vous voulez des échantillons indépendants | Stream.generate |
| Vous voulez les lignes d'un fichier texte | Files.lines(path) dans try-with-resources |
Vous voulez les caractères d'un String | "...".chars() ou .codePoints() |
| Vous voulez un stream vide de repli | Stream.empty() |
| Vous construisez par morceaux | Stream.builder() |
Vous voulez streamer une Map | map.entrySet().stream() |
Ce tableau couvre tout ce qui est dans le chapitre, et probablement 99 % du code réel.
Un exemple complet : dix sources, un programme
Le programme ci-dessous construit un stream depuis chacune des sources principales, exécute un petit terminal dessus pour que la sortie soit visible, et affiche le résultat ainsi que le type de source utilisé.
Ce qu'il faut retenir de l'exécution :
- Chaque ligne de la sortie provient d'une source différente, mais elles s'enchaînent toutes avec le même vocabulaire intermédiaire/terminal. Le choix de la source détermine ce par quoi le pipeline peut commencer ; il ne change pas ce qui vient après.
Arrays.stream(int[])a produit unIntStream—sum()est directement disponible sur le stream, sans boxing, sansCollectors.summingInt. Les spécialisations primitives comptent sur les pipelines numériques.- Les deux appels
Stream.iteratemontrent la différence entreiterate(seed, f)+limit(n)(vous choisissez le nombre) et leiterate(seed, hasNext, next)à 3 arguments (la source choisit le nombre). Les deux sont bornés ; uniteratesans borne et sans terminal en court-circuit est le bug classique qui bloque la JVM pour toujours. Stream.empty()etOptional.of(...).stream()sont la façon dont les streams vides et à un seul élément entrent dans un pipeline — typiquement dans une brancheflatMapoù certaines entrées produisent zéro ou un élément en aval.Stream.builder()est la trappe de secours pour le cas (rare) où la source est construite de manière impérative à travers des branches. La plupart du code réel se tourne d'abord verscoll.stream().
Et ensuite
Vous pouvez désormais construire n'importe quel stream depuis n'importe quelle source dont vous disposez. Les deux prochains chapitres couvrent les opérations qui s'exécutent entre la source et le résultat. D'abord, Java Stream Intermediate Operations — filter, map, flatMap, distinct, sorted, peek, limit, skip — les transformations paresseuses qui remodèlent le stream sans l'exécuter. Puis les terminaux qui produisent la valeur.