W3docs

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 Collectioncoll.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();   // 55

range(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, ..., 512

Esiste 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 empty

Optional.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

SituazioneUsa
Hai già una List, Set, Queuecoll.stream()
Hai pochi elementi fissiStream.of(a, b, c)
Hai un T[]Arrays.stream(arr)
Hai int[], long[], double[]Arrays.stream(arr) → stream primitivo
Vuoi iterare per indiceIntStream.range(0, n)
Vuoi ogni termine dal precedenteStream.iterate
Vuoi campioni indipendentiStream.generate
Vuoi le righe di un file di testoFiles.lines(path) dentro try-with-resources
Vuoi i caratteri di una String"...".chars() o .codePoints()
Vuoi uno stream vuoto di fallbackStream.empty()
Stai costruendo pezzo per pezzoStream.builder()
Vuoi trasmettere una Mapmap.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.

java— editable, runs on the server

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 un IntStreamsum() è direttamente sullo stream, senza boxing, senza Collectors.summingInt. Le specializzazioni primitive contano nelle pipeline numeriche.
  • Le due chiamate a Stream.iterate mostrano la differenza tra iterate(seed, f) + limit(n) (sei tu a scegliere il conteggio) e l'iterate a 3 argomenti iterate(seed, hasNext, next) (la sorgente sceglie il conteggio). Entrambi sono limitati; un iterate senza limite e senza un terminale di cortocircuito è il classico bug che blocca la JVM per sempre.
  • Stream.empty() e Optional.of(...).stream() sono il modo in cui gli stream vuoti e a elemento singolo entrano in una pipeline — tipicamente all'interno di un ramo flatMap dove 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 a coll.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 Operationsfilter, map, flatMap, distinct, sorted, peek, limit, skip — le trasformazioni lazy che rimodellano lo stream senza eseguirlo. Poi i terminali che producono il valore.

Esercitati

Pratica
Qual è il modo *meno costoso* per iterare gli interi da `0` a `99`, in ordine, come stream?
Qual è il modo *meno costoso* per iterare gli interi da `0` a `99`, in ordine, come stream?
Was this page helpful?