Creare Stream Java
Crea stream Java da collezioni, array, Stream.of, Stream.iterate, Stream.generate e sorgenti I/O.
Il capitolo introduttivo ha mostrato la struttura della pipeline — sorgente → operazioni intermedie → terminale — e ha trattato la sorgente come un dato acquisito. Questo capitolo è il catalogo delle sorgenti. Ogni pipeline di stream che scrivi inizia con una di esse, e ognuna ha un piccolo insieme di caratteristiche che determinano se la pipeline risultante è corretta, lazy, finita, ordinata o parallelizzabile.
L'elenco è più breve di quanto sembri. Quasi ogni stream che scriverai inizia con uno di coll.stream(), Stream.of(...), Arrays.stream(arr), o un IntStream.range. Il resto di questo capitolo è "e queste sono le poche situazioni in cui le altre sono la scelta giusta."
Da una Collection — coll.stream()
La sorgente dominante. Collection<T> ha un metodo stream() di default, quindi ogni List, Set, Queue e Deque ne espone uno gratuitamente:
List<String> names = List.of("Alice", "Bob", "Carol");
long count = names.stream().filter(n -> n.length() > 3).count();Lo stream è sequenziale, dimensionato (la JVM conosce il numero di elementi in anticipo) e ordinato se la collezione lo è. Una List produce uno stream ordinato; un HashSet produce uno stream non ordinato; un TreeSet produce uno stream ordinato secondo il comparatore dell'insieme.
Esiste anche coll.parallelStream(), che pianifica l'esecuzione attraverso il ForkJoinPool comune. Stessa sorgente, diversa politica di esecuzione — trattata in Java Parallel Streams.
Da elementi espliciti — Stream.of(...)
Usa Stream.of quando hai un breve elenco noto di elementi e non vuoi creare una List usa e getta:
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");È un metodo varargs, quindi accetta qualsiasi numero di argomenti (zero è consentito e produce uno stream vuoto). Con un singolo argomento T[] il compilatore sceglie Stream.of(T...), non Stream.of(T) — utile quando hai già un array:
String[] arr = {"x", "y", "z"};
Stream<String> fromArr = Stream.of(arr); // same as Arrays.stream(arr)Da un array — Arrays.stream(...)
Arrays.stream ha overload per T[], int[], long[], double[], più varianti con range:
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);Gli overload primitivi restituiscono IntStream, LongStream, DoubleStream — non Stream<Integer>. Questo è importante: gli stream primitivi evitano il boxing, hanno sum, average, min, max direttamente (senza collector), e si integrano bene con mapToInt/mapToObj per spostarsi tra i due mondi.
Range primitivi — IntStream.range / rangeClosed
Il modo più veloce per iterare per indice senza un ciclo 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) è semi-aperto [a, b). rangeClosed(a, b) è [a, b]. Entrambi sono limitati, ordinati, dimensionati e più veloci di Stream.iterate(0, i -> i + 1).limit(n) perché la JVM conosce il conteggio in anticipo. Usali ogni volta che il corpo di un ciclo è "fai qualcosa all'indice i."
Per accoppiare un indice agli elementi di una List si scrive:
List<String> names = List.of("Alice", "Bob", "Carol");
IntStream.range(0, names.size())
.mapToObj(i -> i + ": " + names.get(i))
.forEach(System.out::println);Stream infiniti generati — Stream.iterate e Stream.generate
Due modi per produrre uno stream illimitato. Sembrano simili; non sono la stessa cosa.
Stream.iterate(seed, f) — inizia con seed, poi f(seed), poi f(f(seed)), …. Ordinato, deterministico, sequenziale. Quasi sempre seguito da un'operazione di cortocircuito:
Stream.iterate(1, n -> n * 2)
.limit(10)
.forEach(System.out::println); // 1, 2, 4, 8, ..., 512Esiste anche un overload a 3 argomenti Stream.iterate(seed, hasNext, next) (Java 9+) che incorpora la condizione di stop nella sorgente — nessun limit necessario:
Stream.iterate(1, n -> n < 1000, n -> n * 2).forEach(System.out::println);Stream.generate(supplier) — chiama un Supplier<T> ripetutamente. Non ordinato, nessuna relazione tra gli elementi:
Stream.generate(Math::random).limit(5).forEach(System.out::println);
Stream.generate(() -> "ping").limit(3).forEach(System.out::println);Usa iterate per sequenze in cui ogni termine dipende dal precedente (n -> n + 1, n -> n * 2, la coppia Fibonacci arr -> {arr[1], arr[0] + arr[1]}). Usa generate per valori indipendenti da una sorgente laterale — numeri casuali, costanti fisse, UUID.
In entrambi i casi, termina sempre con un'operazione di cortocircuito: limit(n), l'iterate a 3 argomenti, o un terminale come findFirst / anyMatch. Un semplice toList() su uno stream infinito blocca la JVM.
Da I/O — Files.lines, BufferedReader.lines
Files.lines(path) apre un file e restituisce uno Stream<String> delle sue righe. Lazy: le righe vengono lette man mano che la pipeline le richiede, non tutte in anticipo:
try (Stream<String> lines = Files.lines(Path.of("words.txt"))) {
long longWords = lines.filter(w -> w.length() > 8).count();
}Il try-with-resources è obbligatorio. Lo stream tiene aperto un file handle, e l'unico modo per rilasciarlo è chiamare close() — che il try-with-resources fa per te. Senza di esso il descrittore si perde finché lo stream non viene garbage-collected, il che potrebbe non avvenire mai sotto carico.
Stessa struttura per i Reader tramite BufferedReader.lines(). Entrambi sono il modo canonico per scorrere un file di testo senza caricarlo in memoria.
String.chars() e String.codePoints()
Una String è una sequenza di unità di codice UTF-16; l'API espone entrambe le viste:
"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);Entrambi restituiscono IntStream. chars() va bene per ASCII; per tutto ciò che potrebbe contenere coppie surrogate (la maggior parte degli emoji, molti script), codePoints() è la scelta sicura.
Stream vuoti e a elemento singolo
Per i casi predefiniti e i rami di 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+) è il ponte tra Optional<T> e Stream<T> — utile quando si applica flatMap a uno stream di Optional per ottenere uno stream di valori presenti senza alcuna gestione dei null.
Stream.Builder — aggiungere elementi uno alla volta
Quando non riesci a esprimere la sorgente come un letterale, un array o un generatore — di solito perché gli elementi provengono da rami disparati di codice imperativo — esiste un builder:
Stream.Builder<String> b = Stream.builder();
b.add("first");
if (someCondition) b.add("second");
b.accept("third");
Stream<String> s = b.build();Dopo build() il builder è sigillato; ulteriori add lanciano un'eccezione. È uno strumento raro ma legittimo. La maggior parte del codice che lo utilizza è meglio scritta con un ArrayList<String> seguito da list.stream(), ma il builder evita quella struttura intermedia quando i dati vengono costruiti pezzo per pezzo.
Stream per Map — non esiste
Map<K, V> non ha un metodo stream(). Si trasmettono invece le sue viste:
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() è ciò che si vuole nella maggior parte dei casi — entrambe le metà di ogni entry sono nello scope, e Map.Entry::getKey / ::getValue funzionano come riferimenti a metodo.
Scegliere la sorgente giusta
| Situazione | Usa |
|---|---|
Hai già una List, Set, Queue | coll.stream() |
| Hai pochi elementi fissi | Stream.of(a, b, c) |
Hai un T[] | Arrays.stream(arr) |
Hai int[], long[], double[] | Arrays.stream(arr) → stream primitivo |
| Vuoi iterare per indice | IntStream.range(0, n) |
| Vuoi ogni termine dal precedente | Stream.iterate |
| Vuoi campioni indipendenti | Stream.generate |
| Vuoi le righe di un file di testo | Files.lines(path) dentro try-with-resources |
Vuoi i caratteri di una String | "...".chars() o .codePoints() |
| Vuoi uno stream vuoto di fallback | Stream.empty() |
| Stai costruendo pezzo per pezzo | Stream.builder() |
Vuoi trasmettere una Map | map.entrySet().stream() |
Quella tabella copre tutto ciò che è nel capitolo, e probabilmente il 99% del codice reale.
Un esempio pratico: dieci sorgenti, un programma
Il programma seguente costruisce uno stream da ciascuna delle principali sorgenti, esegue un piccolo terminale su di esso per rendere visibile l'output, e stampa sia il risultato che il tipo di sorgente da cui proviene.
Cosa ricavare dall'esecuzione:
- Ogni riga nell'output proviene da una sorgente diversa, ma tutte confluiscono nel medesimo vocabolario di operazioni intermedie e terminali. La scelta della sorgente determina il punto di partenza della pipeline; non cambia ciò che viene dopo.
Arrays.stream(int[])ha prodotto unIntStream—sum()è direttamente sullo stream, senza boxing, senzaCollectors.summingInt. Le specializzazioni primitive contano nelle pipeline numeriche.- Le due chiamate a
Stream.iteratemostrano la differenza traiterate(seed, f)+limit(n)(sei tu a scegliere il conteggio) e l'iteratea 3 argomentiiterate(seed, hasNext, next)(la sorgente sceglie il conteggio). Entrambi sono limitati; uniteratesenza limite e senza un terminale di cortocircuito è il classico bug che blocca la JVM per sempre. Stream.empty()eOptional.of(...).stream()sono il modo in cui gli stream vuoti e a elemento singolo entrano in una pipeline — tipicamente all'interno di un ramoflatMapdove alcuni input producono zero o un elemento a valle.Stream.builder()è la via di fuga per il caso (raro) in cui la sorgente viene costruita imperativamente tra più rami. La maggior parte del codice reale ricorre prima acoll.stream().
Cosa c'è dopo
Ora puoi costruire qualsiasi stream di cui hai bisogno da qualsiasi sorgente che hai effettivamente a disposizione. I prossimi due capitoli trattano le operazioni che si eseguono tra la sorgente e il risultato. Prima, Java Stream Intermediate Operations — filter, map, flatMap, distinct, sorted, peek, limit, skip — le trasformazioni lazy che rimodellano lo stream senza eseguirlo. Poi i terminali che producono il valore.