Stream în Java

Un șir de valori prelucrate pas cu pas: filtrezi, transformi, sortezi și aduni rezultatul, fără bucle scrise de mână.

Sintaxă
import java.util.stream.Stream;

public interface Stream<T> extends BaseStream<T, Stream<T>>

Ce face Stream

Un Stream<T> este o „bandă rulantă” de valori de tip T. Nu păstrează datele (nu este o colecție), ci le trece prin mai multe etape: filter (păstrează doar unele), map (le transformă), sorted (le ordonează) și, la final, o operație care produce rezultatul: toList(), count(), collect(...), forEach(...).

Etapele din mijloc se numesc operații intermediare: returnează un nou stream și nu fac nimic până nu apare operația finală (stream-ul este „leneș”). Un stream se poate parcurge o singură dată; după operația finală trebuie creat altul.

Cel mai des obții un stream dintr-o colecție, cu lista.stream(), sau direct din valori, cu Stream.of(...). Pentru numere există variantele IntStream, LongStream și DoubleStream, care au sum(), average() și nu împachetează numerele în obiecte.

Ce returnează

Este o interfață. Metodele intermediare returnează un nou Stream; metodele finale returnează rezultatul (o listă, un număr, un Optional) sau nimic (forEach).

Exemple

Exemplul 1: Bucla clasică și varianta cu stream

Ambele variante aleg orașele cu mai mult de 4 litere, le scriu cu majuscule și le sortează. Varianta cu stream descrie ce vrei, nu cum se parcurge lista.

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<String> orase = List.of("Iași", "Cluj", "Brașov", "Arad", "Timișoara", "Galați");

        // cu buclă
        List<String> rezultat = new ArrayList<>();
        for (String oras : orase) {
            if (oras.length() > 4) {
                rezultat.add(oras.toUpperCase());
            }
        }
        Collections.sort(rezultat);
        System.out.println(rezultat);

        // cu stream
        List<String> cuStream = orase.stream()
                .filter(oras -> oras.length() > 4)
                .map(String::toUpperCase)
                .sorted()
                .toList();
        System.out.println(cuStream);
    }
}

Rezultat

[BRAȘOV, GALAȚI, TIMIȘOARA]
[BRAȘOV, GALAȚI, TIMIȘOARA]

Exemplul 2: Stream-ul este leneș

Fără operația finală, filter nu rulează deloc. Abia count() pornește prelucrarea.

import java.util.stream.Stream;

public class Main {
    public static void main(String[] args) {
        Stream<String> nume = Stream.of("Ana", "Mihai", "Ioana")
                .filter(n -> {
                    System.out.println("verific " + n);
                    return n.startsWith("I");
                });
        System.out.println("Stream creat, încă nu s-a verificat nimic.");
        long cate = nume.count();
        System.out.println("Nume cu I: " + cate);
    }
}

Rezultat

Stream creat, încă nu s-a verificat nimic.
verific Ana
verific Mihai
verific Ioana
Nume cu I: 1

Exemplul 3: Un stream se folosește o singură dată

A doua operație finală pe același stream aruncă IllegalStateException. Dacă ai nevoie de date de mai multe ori, păstrează lista și creează câte un stream nou.

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

public class Main {
    public static void main(String[] args) {
        List<Integer> note = List.of(9, 7, 10, 5);
        Stream<Integer> s = note.stream();
        System.out.println("Câte note: " + s.count());
        try {
            System.out.println(s.count());
        } catch (IllegalStateException e) {
            System.out.println("Eroare: " + e.getMessage());
        }
        // corect: un stream nou din listă
        System.out.println("Note de trecere: " + note.stream().filter(n -> n >= 5).count());
    }
}

Rezultat

Câte note: 4
Eroare: stream has already been operated upon or closed
Note de trecere: 4

Greșeli frecvente

  • Refolosești un stream după o operație finală. Primești IllegalStateException: stream has already been operated upon or closed. Creează-l din nou din colecție.
  • Scrii doar operații intermediare (filter, map, peek) și te aștepți să se întâmple ceva. Fără o operație finală, nimic nu rulează.
  • Modifici colecția sursă în timp ce o parcurgi cu stream-ul (de exemplu lista.add în forEach). Rezultatul este ConcurrentModificationException sau un comportament greșit.

Comenzi înrudite

  • Stream.of: Creează un stream direct din câteva valori; ofNullable face un stream gol pentru null, iar empty un stream fără elemente.
  • Stream.filter: Păstrează în stream doar elementele care îndeplinesc o condiție.
  • Stream.map: Transformă fiecare element al stream-ului în altă valoare, eventual de alt tip.
  • Stream.collect: Adună elementele într-un rezultat (listă, set, map, text) folosind un Collector, de obicei din clasa Collectors.
  • Stream.toList: Adună elementele stream-ului într-o listă care nu se poate modifica, sau într-un tablou cu toArray.
  • IntStream: Un stream de numere int simple, cu sum, average, min, max; IntStream.of îl creează direct din valori.
  • Collectors: Clasa cu rețete gata făcute pentru collect: liste, mulțimi, map-uri, texte unite, grupări, numărări și medii.