Gli Stream in Java sono stati introdotti con Java 8 e rappresentano uno degli strumenti più importanti per lavorare con collezioni, array, file, sequenze di dati e pipeline di elaborazione in stile funzionale.

Quando si parla di creare Stream in Java, non esiste un solo modo per farlo. Possiamo creare uno stream vuoto, partire da una collezione, da un array, da valori espliciti, da un builder, da una funzione generatrice, da un file, da un range numerico o da una sorgente potenzialmente infinita.

Conoscere i diversi modi per creare uno Stream è utile perché ogni approccio ha un caso d’uso specifico. Un conto è trasformare una List in uno stream, un altro è leggere righe da un file, generare numeri casuali, evitare null o creare sequenze infinite con Stream.generate() e Stream.iterate().

In breve: uno Stream è una sequenza di elementi che può essere elaborata con operazioni come filter, map, sorted, limit, collect e forEach. La creazione dello stream è il punto di partenza della pipeline.

Indice dei contenuti

Cos’è uno Stream in Java

Uno Stream in Java è una sequenza di elementi su cui è possibile eseguire operazioni di elaborazione in modo dichiarativo. A differenza di una collezione, uno stream non è una struttura dati che contiene elementi. È piuttosto una vista o una pipeline di elaborazione su una sorgente dati.

La sorgente può essere:

  • una List;
  • un Set;
  • una Collection;
  • un array;
  • un file;
  • una funzione generatrice;
  • un range numerico;
  • un Optional;
  • una Map tramite entry, chiavi o valori;
  • una sequenza infinita limitata successivamente.

Una pipeline Stream è composta da una sorgente, zero o più operazioni intermedie e un’operazione terminale.

List<String> names = List.of("Mario", "Luca", "Anna");

List<String> result = names.stream()
        .filter(name -> name.length() > 4)
        .map(String::toUpperCase)
        .toList();

In questo esempio:

  • names.stream() crea lo stream;
  • filter() seleziona alcuni elementi;
  • map() trasforma gli elementi;
  • toList() chiude la pipeline e produce il risultato.

Concetto chiave: uno Stream può essere consumato una sola volta. Dopo un’operazione terminale, non può essere riutilizzato.

Creare uno Stream vuoto con Stream.empty()

Il primo modo per creare uno stream è usare Stream.empty(). Questo metodo restituisce uno stream vuoto, cioè senza elementi.

È molto utile quando vuoi evitare di restituire null da un metodo che dovrebbe produrre uno stream.

import java.util.List;
import java.util.stream.Stream;

public class StreamExamples {

    public Stream<String> streamOf(List<String> list) {
        return list == null || list.isEmpty()
                ? Stream.empty()
                : list.stream();
    }
}

Questo approccio è preferibile rispetto a restituire null, perché chi usa il metodo può continuare a lavorare con una pipeline Stream senza controlli aggiuntivi.

Esempio di utilizzo

Stream<String> stream = streamOf(null);

stream.forEach(System.out::println);

Il codice non stampa nulla, ma non genera nemmeno una NullPointerException.

Best practice: se un metodo deve restituire uno Stream e non ci sono elementi, restituisci Stream.empty() invece di null.

Creare uno Stream da Collection, List e Set

Il modo più comune per creare uno stream è partire da una collezione Java. Tutte le classi che implementano java.util.Collection espongono il metodo stream().

Questo vale per:

  • List;
  • Set;
  • Queue;
  • Collection generiche;
  • altre implementazioni del framework Collections.

Stream da Collection

import java.util.Arrays;
import java.util.Collection;

public class CollectionStreamExample {

    public static void main(String[] args) {
        Collection<String> collection = Arrays.asList("A", "B", "C", "D");

        collection.stream()
                .forEach(System.out::println);
    }
}

Stream da List

import java.util.List;

public class ListStreamExample {

    public static void main(String[] args) {
        List<String> list = List.of("Java", "Spring", "Hibernate");

        list.stream()
                .map(String::toUpperCase)
                .forEach(System.out::println);
    }
}

Stream da Set

import java.util.HashSet;
import java.util.Set;

public class SetStreamExample {

    public static void main(String[] args) {
        Set<String> set = new HashSet<>();

        set.add("Java");
        set.add("Stream");
        set.add("API");

        set.stream()
                .forEach(System.out::println);
    }
}

Quando lavori con un Set, ricorda che l’ordine degli elementi può non essere garantito, a seconda dell’implementazione usata.

Stream da List con filtro

List<String> languages = List.of("Java", "Kotlin", "Scala", "Groovy");

List<String> result = languages.stream()
        .filter(language -> language.length() > 5)
        .toList();

System.out.println(result);

Questo è uno degli usi più frequenti degli Stream: partire da una lista, filtrare elementi e raccogliere il risultato.

Creare uno Stream da array con Arrays.stream()

Un altro modo molto comune per creare uno Stream è partire da un array usando Arrays.stream().

Stream da array completo

import java.util.Arrays;
import java.util.stream.Stream;

public class ArrayStreamExample {

    public static void main(String[] args) {
        String[] array = new String[] { "a", "b", "c" };

        Stream<String> stream = Arrays.stream(array);

        stream.forEach(System.out::println);
    }
}

Stream da una parte dell’array

Arrays.stream() permette anche di specificare un indice iniziale e uno finale.

String[] array = new String[] { "a", "b", "c", "d", "e" };

Stream<String> partialStream = Arrays.stream(array, 1, 4);

partialStream.forEach(System.out::println);

Questo esempio stampa:

b
c
d

L’indice iniziale è incluso, mentre l’indice finale è escluso.

Stream da array di primitivi

Se l’array contiene tipi primitivi, Arrays.stream() restituisce stream specializzati come IntStream, LongStream o DoubleStream.

int[] numbers = {1, 2, 3, 4, 5};

int sum = Arrays.stream(numbers)
        .sum();

System.out.println(sum);

In questo caso non otteniamo uno Stream<Integer>, ma un IntStream.

Creare uno Stream con Stream.of()

La classe Stream offre il metodo statico of(), che permette di creare uno stream partendo da uno o più valori espliciti.

Stream con più valori

import java.util.stream.Stream;

public class StreamOfExample {

    public static void main(String[] args) {
        Stream<String> stream = Stream.of("a", "b", "c");

        stream.forEach(System.out::println);
    }
}

Stream con un solo valore

Stream<String> single = Stream.of("Java");

single.forEach(System.out::println);

Stream da array con Stream.of()

String[] array = { "Java", "Spring", "Hibernate" };

Stream<String> stream = Stream.of(array);

stream.forEach(System.out::println);

In questo caso Stream.of(array) crea uno stream degli elementi dell’array, perché il tipo viene inferito correttamente come String[].

Attenzione con array primitivi

Con array di primitivi bisogna fare attenzione.

int[] numbers = {1, 2, 3};

Stream<int[]> stream = Stream.of(numbers);

Questo non crea uno stream di interi, ma uno stream con un solo elemento: l’array int[]. Se vuoi uno stream di interi primitivi, usa Arrays.stream(numbers).

IntStream intStream = Arrays.stream(numbers);

Regola pratica: per array di oggetti puoi usare Stream.of() o Arrays.stream(). Per array primitivi, preferisci Arrays.stream().

Creare uno Stream con Stream.builder()

Un altro modo per creare uno stream è usare il builder fornito da Stream.builder().

Questo approccio è utile quando vuoi costruire uno stream elemento per elemento, in modo esplicito.

import java.util.stream.Stream;

public class StreamBuilderExample {

    public static void main(String[] args) {
        Stream<String> stream = Stream.<String>builder()
                .add("java")
                .add("boss")
                .add("stream")
                .build();

        stream.forEach(System.out::println);
    }
}

Il tipo <String> viene spesso indicato esplicitamente per aiutare l’inferenza del compilatore.

Builder con variabile

Stream.Builder<String> builder = Stream.builder();

builder.add("A");
builder.add("B");
builder.add("C");

Stream<String> stream = builder.build();

stream.forEach(System.out::println);

Attenzione: dopo aver chiamato build(), il builder non dovrebbe essere più usato per aggiungere elementi.

Creare Stream infiniti con Stream.generate()

Il metodo Stream.generate() crea uno stream potenzialmente infinito usando un Supplier.

Un Supplier<T> è un’interfaccia funzionale che non riceve input e produce un valore di tipo T.

Stream infinito di stringhe

import java.util.stream.Stream;

public class GenerateExample {

    public static void main(String[] args) {
        Stream<String> stream = Stream.generate(() -> "javaboss")
                .limit(10);

        stream.forEach(System.out::println);
    }
}

Poiché generate() crea uno stream infinito, quasi sempre è necessario usare limit() per evitare una pipeline senza fine.

Stream di numeri casuali

import java.util.Random;
import java.util.stream.Stream;

public class RandomStreamExample {

    public static void main(String[] args) {
        Random random = new Random();

        Stream<Integer> numbers = Stream.generate(() -> random.nextInt(100))
                .limit(5);

        numbers.forEach(System.out::println);
    }
}

Stream di oggetti generati

Stream<User> users = Stream.generate(User::new)
        .limit(3);

Questo approccio può essere utile per test, mock data o generazione controllata di oggetti.

Da ricordare: Stream.generate() produce uno stream infinito se non viene limitato.

Creare Stream con Stream.iterate()

Il metodo Stream.iterate() crea uno stream partendo da un valore iniziale e applicando una funzione per generare il valore successivo.

Stream infinito con iterate

import java.util.stream.Stream;

public class IterateExample {

    public static void main(String[] args) {
        Stream<Integer> stream = Stream.iterate(1, n -> n * 2)
                .limit(5);

        stream.forEach(System.out::println);
    }
}

Output:

1
2
4
8
16

Anche in questo caso lo stream è potenzialmente infinito, quindi limit() è spesso necessario.

Stream.iterate con condizione da Java 9

Da Java 9 è disponibile una variante di Stream.iterate() con tre parametri:

Stream.iterate(seed, hasNext, next)

Questa versione permette di indicare una condizione di terminazione senza usare limit().

Stream<Integer> numbers = Stream.iterate(
        1,
        n -> n <= 16,
        n -> n * 2
);

numbers.forEach(System.out::println);

Output:

1
2
4
8
16

Questa variante è più leggibile quando la sequenza deve terminare in base a una condizione logica.

Creare uno Stream nullable con Stream.ofNullable()

Stream.ofNullable() è stato introdotto in Java 9 ed è molto utile quando un valore può essere null.

Se il valore non è nullo, crea uno stream con un elemento. Se il valore è nullo, crea uno stream vuoto.

Esempio base

String value = "Java";

Stream<String> stream = Stream.ofNullable(value);

stream.forEach(System.out::println);

Esempio con valore null

String value = null;

Stream<String> stream = Stream.ofNullable(value);

stream.forEach(System.out::println);

Il secondo esempio non stampa nulla e non genera errori.

Uso pratico con flatMap

List<String> values = List.of("Java", "Spring", "Hibernate");

Stream<String> result = values.stream()
        .flatMap(value -> Stream.ofNullable(findDescription(value)));

Questo approccio evita controlli manuali su null dentro la pipeline.

Nota: Stream.ofNullable() è utile quando vuoi integrare valori potenzialmente nulli in una pipeline Stream in modo pulito.

Unire Stream con Stream.concat()

Stream.concat() permette di creare un nuovo stream concatenando due stream esistenti.

Stream<String> first = Stream.of("A", "B");
Stream<String> second = Stream.of("C", "D");

Stream<String> result = Stream.concat(first, second);

result.forEach(System.out::println);

Output:

A
B
C
D

Questo metodo è utile quando devi combinare due sorgenti. Tuttavia, se devi unire molti stream, spesso è più leggibile usare flatMap().

Unire più stream con flatMap

Stream<String> result = Stream.of(
        Stream.of("A", "B"),
        Stream.of("C", "D"),
        Stream.of("E", "F")
).flatMap(stream -> stream);

result.forEach(System.out::println);

Creare Stream da file con Files.lines()

Java NIO permette di creare uno stream a partire da un file di testo tramite Files.lines().

Ogni riga del file diventa un elemento dello stream.

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.stream.Stream;

public class FileStreamExample {

    public static void main(String[] args) throws IOException {
        Path path = Path.of("file.txt");

        try (Stream<String> lines = Files.lines(path)) {
            lines.forEach(System.out::println);
        }
    }
}

È importante usare try-with-resources, perché lo stream restituito da Files.lines() mantiene aperta una risorsa I/O che deve essere chiusa correttamente.

Filtrare righe da file

Path path = Path.of("logs.txt");

try (Stream<String> lines = Files.lines(path)) {
    lines.filter(line -> line.contains("ERROR"))
            .forEach(System.out::println);
}

Contare le righe di un file

Path path = Path.of("file.txt");

try (Stream<String> lines = Files.lines(path)) {
    long count = lines.count();
    System.out.println(count);
}

Best practice: quando crei uno Stream da file, chiudilo sempre con try-with-resources.

Stream di tipi primitivi: IntStream, LongStream e DoubleStream

Poiché Stream<T> lavora con oggetti e non con tipi primitivi, Java mette a disposizione stream specializzati per evitare boxing e unboxing inutili:

  • IntStream per int;
  • LongStream per long;
  • DoubleStream per double.

IntStream.range()

IntStream.range(start, end) genera numeri da start incluso a end escluso.

import java.util.stream.IntStream;

public class IntStreamExample {

    public static void main(String[] args) {
        IntStream.range(5, 10)
                .forEach(System.out::println);
    }
}

Output:

5
6
7
8
9

IntStream.rangeClosed()

rangeClosed() include anche il valore finale.

IntStream.rangeClosed(5, 10)
        .forEach(System.out::println);

Output:

5
6
7
8
9
10

LongStream

import java.util.stream.LongStream;

LongStream longStream = LongStream.rangeClosed(1, 5);

longStream.forEach(System.out::println);

DoubleStream

import java.util.stream.DoubleStream;

DoubleStream doubleStream = DoubleStream.of(1.5, 2.5, 3.5);

doubleStream.forEach(System.out::println);

Operazioni numeriche utili

Gli stream primitivi offrono metodi comodi per calcoli numerici.

int sum = IntStream.rangeClosed(1, 100)
        .sum();

double average = IntStream.rangeClosed(1, 100)
        .average()
        .orElse(0);

int max = IntStream.of(3, 8, 2, 10)
        .max()
        .orElse(0);

Consiglio: se lavori con numeri primitivi, usa IntStream, LongStream o DoubleStream invece di Stream<Integer>, Stream<Long> o Stream<Double> quando possibile.

Creare Stream di numeri casuali

La classe Random permette di creare stream primitivi di numeri casuali.

DoubleStream casuale

import java.util.Random;
import java.util.stream.DoubleStream;

public class RandomDoubleStreamExample {

    public static void main(String[] args) {
        Random random = new Random();

        DoubleStream stream = random.doubles(3);

        stream.forEach(System.out::println);
    }
}

IntStream casuale con range

Random random = new Random();

random.ints(5, 1, 100)
        .forEach(System.out::println);

Questo genera 5 numeri interi casuali tra 1 incluso e 100 escluso.

LongStream casuale

Random random = new Random();

random.longs(3)
        .forEach(System.out::println);

Gli stream casuali sono utili per test, simulazioni, generazione di dati e prototipi.

Creare Stream da stringhe e regex

Un altro caso utile è creare uno stream partendo da una stringa, per esempio dividendo un testo in parole.

Con Pattern.splitAsStream()

import java.util.regex.Pattern;
import java.util.stream.Stream;

public class RegexStreamExample {

    public static void main(String[] args) {
        String text = "java,spring,hibernate,stream";

        Stream<String> stream = Pattern.compile(",")
                .splitAsStream(text);

        stream.forEach(System.out::println);
    }
}

Con String.split() e Arrays.stream()

String text = "java spring hibernate stream";

Stream<String> words = Arrays.stream(text.split(" "));

words.forEach(System.out::println);

Se devi lavorare spesso con regex, Pattern.splitAsStream() può essere più diretto.

Creare Stream da Optional

Da Java 9, Optional offre il metodo stream(). Questo metodo restituisce uno stream con un solo elemento se l’Optional contiene un valore, oppure uno stream vuoto se l’Optional è vuoto.

Esempio base

Optional<String> optional = Optional.of("Java");

Stream<String> stream = optional.stream();

stream.forEach(System.out::println);

Uso pratico con flatMap

List<Optional<String>> values = List.of(
        Optional.of("Java"),
        Optional.empty(),
        Optional.of("Spring")
);

List<String> result = values.stream()
        .flatMap(Optional::stream)
        .toList();

System.out.println(result);

Output:

[Java, Spring]

Questo approccio evita controlli manuali e rende la pipeline più pulita.

Creare Stream da Map

Una Map non implementa Collection, quindi non espone direttamente il metodo stream(). Tuttavia, possiamo creare stream partendo da:

  • entrySet();
  • keySet();
  • values().

Stream delle entry

Map<String, Integer> scores = Map.of(
        "Mario", 10,
        "Luca", 8,
        "Anna", 9
);

scores.entrySet()
        .stream()
        .forEach(entry -> System.out.println(entry.getKey() + ": " + entry.getValue()));

Stream delle chiavi

scores.keySet()
        .stream()
        .forEach(System.out::println);

Stream dei valori

scores.values()
        .stream()
        .forEach(System.out::println);

Filtrare una Map con Stream

Map<String, Integer> filtered = scores.entrySet()
        .stream()
        .filter(entry -> entry.getValue() >= 9)
        .collect(Collectors.toMap(
                Map.Entry::getKey,
                Map.Entry::getValue
        ));

Questo è un caso molto comune quando si vuole filtrare una mappa e costruirne una nuova.

Stream sequenziali e paralleli

Quando crei uno stream da una collezione, puoi scegliere tra stream sequenziale e stream parallelo.

Stream sequenziale

List<String> list = List.of("A", "B", "C");

list.stream()
        .forEach(System.out::println);

Stream parallelo

list.parallelStream()
        .forEach(System.out::println);

Uno stream parallelo può eseguire alcune operazioni su più thread, ma non è automaticamente più veloce. Dipende dal tipo di operazione, dalla dimensione dei dati, dal costo di elaborazione e dalla possibilità di dividere il lavoro in modo efficiente.

Convertire uno stream in parallelo

list.stream()
        .parallel()
        .forEach(System.out::println);

Convertire uno stream in sequenziale

list.parallelStream()
        .sequential()
        .forEach(System.out::println);

Attenzione: non usare parallelStream() solo perché sembra più performante. Testa sempre il caso reale.

Errori comuni nella creazione degli Stream

Restituire null invece di Stream.empty()

Se un metodo deve restituire uno stream, è meglio restituire Stream.empty() quando non ci sono elementi.

// Da evitare
return null;

// Meglio
return Stream.empty();

Riutilizzare uno Stream già consumato

Uno stream può essere usato una sola volta.

Stream<String> stream = Stream.of("A", "B", "C");

stream.forEach(System.out::println);

// Errore: lo stream è già stato consumato
stream.count();

Se devi eseguire più operazioni, ricrea lo stream dalla sorgente.

Supplier<Stream<String>> supplier = () -> Stream.of("A", "B", "C");

supplier.get().forEach(System.out::println);
long count = supplier.get().count();

Dimenticare limit() con stream infiniti

Stream.generate() e alcune forme di Stream.iterate() creano stream infiniti.

// Attenzione: stream infinito
Stream.generate(() -> "Java")
        .forEach(System.out::println);

Meglio limitare il numero di elementi.

Stream.generate(() -> "Java")
        .limit(10)
        .forEach(System.out::println);

Non chiudere stream da file

Gli stream creati da file devono essere chiusi.

try (Stream<String> lines = Files.lines(Path.of("file.txt"))) {
    lines.forEach(System.out::println);
}

Confondere Stream.of(array) con Arrays.stream(array)

Con array di oggetti spesso il risultato è quello atteso. Con array primitivi, invece, Stream.of() crea uno stream che contiene l’intero array come singolo elemento.

int[] numbers = {1, 2, 3};

Stream<int[]> wrong = Stream.of(numbers);
IntStream correct = Arrays.stream(numbers);

Usare stream paralleli senza motivo

Gli stream paralleli non sono sempre più efficienti e possono introdurre problemi se la pipeline usa stato condiviso o operazioni non thread-safe.

Best practice per creare Stream in Java

  • Usa collection.stream() quando parti da una collezione.
  • Usa Arrays.stream() quando parti da un array.
  • Usa Stream.empty() invece di restituire null.
  • Usa Stream.of() per pochi valori espliciti.
  • Usa Stream.ofNullable() quando un valore può essere nullo.
  • Usa Stream.builder() quando vuoi costruire uno stream elemento per elemento.
  • Usa Stream.generate() per valori generati da un Supplier.
  • Usa Stream.iterate() per sequenze iterative.
  • Usa Files.lines() con try-with-resources per leggere file riga per riga.
  • Usa IntStream, LongStream e DoubleStream per tipi primitivi.
  • Non riutilizzare uno stream già consumato.
  • Limita sempre gli stream infiniti quando serve.
  • Non usare parallelStream() senza testare prestazioni e sicurezza.

Tabella riassuntiva: modi per creare Stream in Java

Metodo Quando usarlo Esempio
Stream.empty() Quando non ci sono elementi Stream.empty()
collection.stream() Da List, Set, Queue, Collection list.stream()
Arrays.stream() Da array Arrays.stream(array)
Stream.of() Da valori espliciti Stream.of("A", "B")
Stream.builder() Costruzione manuale Stream.builder().add("A").build()
Stream.generate() Stream infinito da Supplier Stream.generate(Math::random)
Stream.iterate() Sequenze iterative Stream.iterate(1, n -> n + 1)
Stream.ofNullable() Valore potenzialmente null Stream.ofNullable(value)
Files.lines() Righe di un file Files.lines(path)
IntStream.range() Range numerico IntStream.range(1, 10)

FAQ su come creare Stream in Java

Come creare uno Stream in Java?

Puoi creare uno Stream in Java usando diversi metodi, tra cui collection.stream(), Arrays.stream(), Stream.of(), Stream.empty(), Stream.builder(), Stream.generate(), Stream.iterate() e Files.lines().

Come creare uno Stream vuoto in Java?

Per creare uno stream vuoto si usa Stream.empty().

Stream<String> empty = Stream.empty();

Come creare uno Stream da una List?

Puoi usare il metodo stream() della lista.

List<String> list = List.of("A", "B", "C");

Stream<String> stream = list.stream();

Come creare uno Stream da un array?

Puoi usare Arrays.stream(array).

String[] array = {"A", "B", "C"};

Stream<String> stream = Arrays.stream(array);

Qual è la differenza tra Stream.of() e Arrays.stream()?

Stream.of() è comodo per valori espliciti o array di oggetti. Arrays.stream() è spesso più chiaro quando parti da un array, soprattutto se si tratta di array primitivi.

Come creare uno Stream infinito?

Puoi creare stream infiniti con Stream.generate() o Stream.iterate(). Di solito devi usare limit() per limitarne la dimensione.

Come creare uno Stream da un file?

Puoi usare Files.lines(path). È consigliato chiudere lo stream con try-with-resources.

try (Stream<String> lines = Files.lines(Path.of("file.txt"))) {
    lines.forEach(System.out::println);
}

Come creare uno Stream di interi?

Puoi usare IntStream.

IntStream.rangeClosed(1, 10)
        .forEach(System.out::println);

Che cos’è Stream.ofNullable()?

Stream.ofNullable() crea uno stream con un elemento se il valore non è nullo, oppure uno stream vuoto se il valore è null.

Uno Stream può essere riutilizzato?

No. Dopo un’operazione terminale, uno stream è consumato e non può essere riutilizzato.

Quando usare parallelStream()?

parallelStream() va usato solo quando hai dati sufficientemente grandi, operazioni indipendenti e un reale beneficio misurato. Non è automaticamente più veloce di uno stream sequenziale.

Conclusione

Esistono molti modi per creare Stream in Java. Il più comune è partire da una collezione con stream(), ma non è l’unico. Puoi creare stream da array, valori espliciti, builder, funzioni generatrici, sequenze iterative, file, range numerici, mappe e valori opzionali.

La scelta del metodo giusto dipende dalla sorgente dei dati e dal tipo di elaborazione che devi eseguire. Se parti da una List, usa list.stream(). Se parti da un array, usa Arrays.stream(). Se vuoi evitare null, usa Stream.empty() o Stream.ofNullable(). Se generi sequenze, usa generate() o iterate(). Se lavori con numeri primitivi, preferisci IntStream, LongStream o DoubleStream.

Gli Stream rendono il codice Java più espressivo, leggibile e orientato alla trasformazione dei dati, ma vanno usati con attenzione. Non bisogna dimenticare che gli stream sono lazy, consumabili una sola volta e, in alcuni casi, legati a risorse da chiudere correttamente.

Riassunto finale: creare uno Stream è semplice, ma scegliere il modo corretto permette di scrivere codice Java più pulito, sicuro e adatto al tipo di sorgente dati che stai elaborando.

Risorse ufficiali utili

Categorie: Core