# Stream API vs Flux: architektonické rozdíly a praktické použití
Porozumění rozdílům mezi Stream API a Flux je klíčové pro Java vývojáře pracující s asynchronními systémy. Navzdory podobnosti metod (filter, map, limit) tyto nástroje řeší zásadně odlišné úkoly. Stream API je určen pro zpracování konečných kolekcí v synchronním režimu, zatímco Flux z Project Reactor je navržen pro reaktivní zpracování proudových dat s podporou backpressure a asynchronnosti. Tento článek odhaluje klíčové architektonické rozdíly a pomáhá vybrat správný nástroj pro konkrétní úkol.
Základy Stream API: synchronní zpracování kolekcí
Stream API je vestavěný mechanismus v Javě pro deklarativní zpracování kolekcí. Jeho klíčová vlastnost: práce s konečnými sadami dat v rámci jednoho vlákna provádění. Při volání .stream() se vytvoří sekvenční proud, který zpracovává prvky ve stejném vlákně, kde byl zahájen (např. main-vlákně).
Důležité je pamatovat na rozdělení operací:
- Meziprodukční operace (filter, map, flatMap, limit, skip) — tvoří řetězec transformací bez okamžitého provedení. Vrátí nový proud a mohou být kombinovány v neomezeném počtu.
- Terminální operace (collect, forEach, reduce) — spustí provedení celého řetězce a uzavřou proud. Po jejich volání není možné proud znovu použít.
Prozkoumejme příklad filtrování a transformace dat:
List<Integer> numbers = List.of(1, 2, 3, 4, 5);
numbers.stream()
.filter(n -> n % 2 == 0)
.map(n -> n * 2)
.forEach(System.out::println); // 4, 8
Zde:
filternechá sudá číslamapzdvojnásobí každý prvekforEachspustí provedení a vypíše výsledek
Kritické omezení Stream API spočívá v jednorázovém použití. Pokus o opakované volání terminální operace vede k IllegalStateException. To je zásadní rozdíl oproti reaktivním proudům.
Flux: reaktivní zpracování proudových dat
Flux z Project Reactor implementuje specifikaci Reactive Streams určenou pro asynchronní zpracování neomezených proudů dat. Na rozdíl od Stream API Flux podporuje:
- Nesynchronní provedení
- Backpressure (regulaci rychlosti spotřeby dat)
- Zpracování chyb prostřednictvím reaktivních operátorů
- Přihlášení k událostem v libovolném vlákně
Klíčové architektonické rozdíly:
- Terminální operace —
.subscribe()je povinná pro spuštění zpracování. Bez ní zůstává Flux „studený“ a negeneruje data. - Lenivá inicializace — data se generují až po přihlášení.
- Podpora neomezených proudů — Flux může pracovat s nekonečnými zdroji (např. senzorová data v reálném čase).
Příklad ekvivalentní operace ve Flux:
Flux.just(1, 2, 3, 4, 5)
.filter(n -> n % 2 == 0)
.map(n -> n * 2)
.subscribe(System.out::println); // 4, 8
Zde .subscribe() nejen spustí zpracování, ale také řídí životní cyklus proudu. Flux může zpracovávat chyby prostřednictvím .onErrorResume() a regulovat proud pomocí .limitRate().
Kritické rozdíly: tabulka porovnání
Pro přehlednost shrneme klíčové rozdíly do strukturovaného seznamu:
- Model provádění
- Stream API: synchronní, blokující zpracování
- Flux: asynchronní, neblokující zpracování s podporou backpressure
- Typ dat
- Stream API: pracuje pouze s konečnými kolekcemi
- Flux: podporuje konečné i nekonečné proudy
- Zpracování chyb
- Stream API: výjimky se šíří přímo (vyžadují try/catch)
- Flux: vestavěné zpracování chyb prostřednictvím operátorů (onErrorResume, retry)
- Opakované použití
- Stream API: jednorázové použití po terminální operaci
- Flux: vícenásobné přihlášení k jednomu proudu (za předpokladu správné konfigurace)
- Paralelismus
- Stream API: .parallelStream() distribuuje úlohy po jádrech CPU
- Flux: .publishOn() a .subscribeOn() řídí kontext provádění
Praktický případ: transformace dat v reálných scénářích
Prozkoumejme úlohu aktualizace dat v kolekci. Máme HashMap se zaměstnanci a jejich stupni. Je požadováno:
- Pro Junior zaměstnance změnit stupeň na Middle
- Přidat označení „(povýšen)"
- Vrátit aktualizovanou strukturu
Realizace přes Stream API:
Map<String, String> updatedEmployees = employees.entrySet().stream()
.map(entry -> {
String grade = entry.getValue();
if ("Junior".equals(grade)) {
return Map.entry(entry.getKey(), "Middle (increased)");
}
return entry;
})
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
Totéž scénář přes Flux:
Map<String, String> updatedEmployees = Flux.fromStream(employees.entrySet().stream())
.map(entry -> {
if ("Junior".equals(entry.getValue())) {
return Map.entry(entry.getKey(), "Middle (increased)");
}
return entry;
})
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue))
.block();
Klíčové rozdíly v realizaci:
- Flux vyžaduje explicitní
.block()pro získání výsledku (porušuje reaktivní přístup) - Stream API blokuje vlákno provádění do dokončení operace
- Pro reaktivní systémy je preferován neblokující přístup přes
.subscribe()
Co je důležité: klíčové závěry
- Stream API — optimální pro synchronní zpracování konečných kolekcí v rámci jednoho vlákna. Používejte pro ETL-operace, transformace dat v obchodní logice.
- Flux — řeší úlohy asynchronního zpracování proudových dat. Používejte při práci se síťovými požadavky, senzory, event bus.
- Backpressure — kritická funkce Flux, která v Stream API chybí. Zajišťuje stabilitu systému při vysoké zátěži.
- Chyby provádění — v Flux se zpracovávají reaktivně, v Stream API vyžadují tradiční try/catch.
- Výkon — Stream API je efektivnější pro krátké synchronní operace, Flux pro dlouhé asynchronní procesy.
— Editorial Team
Zatím žádné komentáře.