Stream è un’interfaccia che permette di elaborare sequenze di dati in modo dichiarativo e funzionale. Uno Stream non memorizza elementi; opera su sorgenti dati come Collection, array o generatori, descrivendo una pipeline di operazioni. Lo Stream separa il cosa fare dai dettagli di implementazione del come iterare, permettendo trasformazioni, filtraggio, aggregazioni e parallelizzazione senza mutare la sorgente.

Package: java.util


TIPI DI STREAM

Java definisce principalmente tre tipologie di Stream:

TipoDescrizione
Stream<T>Per riferimenti a oggetti generici
IntStreamPer valori primitivi int
LongStreamPer valori primitivi long
DoubleStreamPer valori primitivi double

N.B. i tipi primitivi permettono di evitare il boxing automatico e aumentare le prestazioni.


CREAZIONE DI UNO STREAM

Gli Stream non si istanziano direttamente con new. Si ottengono tramite metodi di fabbrica:

List<Integer> numbers = List.of(1,2,3,4,5);
Stream<Integer> s1 = numbers.stream();           // Stream sequenziale
Stream<Integer> s2 = numbers.parallelStream();   // Stream parallelo
 
String[] array = {"a","b","c"};
Stream<String> s3 = Arrays.stream(array);        // Stream da array

OPERAZIONI SUGLI STREAM

Uno Stream utilizza pipeline di operazioni, divise in due categorie:


Operazioni intermedie (lazy)

  • Restituiscono un nuovo Stream
  • Non eseguono subito il calcolo
  • Esempi: filter, map, flatMap, distinct, sorted, limit
Stream<Integer> evenSquares =
    numbers.stream()
           .filter(n -> n % 2 == 0)  // operazione intermedia
           .map(n -> n * n);         // operazione intermedia
           .toList()                 // operazione terminale

Operazioni terminali

  • Consumano lo Stream e producono un risultato
  • Esempi: toList, forEach, collect, reduce, count, anyMatch, allMatch
List<Integer> result = evenSquares.toList();    

In questo caso toList accumula gli elementi in una lista result.


Collectors

Collectors permette di accumulare i risultati in collezioni, mappe o stringhe tramite collect:

Map<String, List<Person>> grouped =
    people.stream()
          .collect(Collectors.groupingBy(Person::getCity));

ESEMPI DI USO

Filtrare e trasformare una lista:

List<Integer> numbers = List.of(1,2,3,4,5,6);
List<Integer> squares =
    numbers.stream()
           .filter(n -> n > 3)
           .map(n -> n * n)
           .toList();
 
System.out.println(squares); // [16, 25, 36]

Flatten di liste annidate:

List<List<String>> listOfLists = List.of(
    List.of("a","b"),
    List.of("c","d")
);
 
List<String> flatList = listOfLists.stream()
                                   .flatMap(List::stream)
                                   .toList();
 
System.out.println(flatList); // [a, b, c, d]

STREAM PARALLELI

Gli Stream paralleli (parallelStream()) in Java sono uno strumento per eseguire le pipeline di uno Stream in più thread contemporaneamente, sfruttando il multi-core della CPU, senza dover gestire esplicitamente i thread.


Funzionamento

Normalmente uno Stream è sequenziale:

List<Integer> numbers = List.of(1,2,3,4,5,6);
numbers.stream()                   // sequenziale
       .filter(n -> n % 2 == 0)
       .map(n -> n * n)
       .forEach(System.out::println);

Tutte le operazioni avvengono su un singolo thread e l’ordine degli elementi è garantito. Per renderlo parallelo, basta usare parallelStream():

List<Integer> numbers = List.of(1,2,3,4,5,6);
numbers.parallelStream()           // parallelo
       .filter(n -> n % 2 == 0)
       .map(n -> n * n)
       .forEach(System.out::println);

Java divide i dati in più blocchi e ogni blocco viene elaborato contemporaneamente su thread diversi. Operazioni intermedie (filter, map, sorted) vengono applicate in parallelo.