V Javě se pod pojmem stream rozumí sekvence prvků, nad kterou je možné provádět operace, ať už sekvenčně nebo paralelně.
Během zpracování streamu se může provést libovolný počet mezikroků, a nakonec terminální operace, po jejímž dokončení je vrácen výsledek.
Co je to stream?
Streamy lze ovládat pomocí Stream API, které bylo zavedeno v Javě 8.
Představte si stream jako výrobní linku, kde je nejprve surovina zpracována, poté roztříděna a nakonec zabalena pro odeslání. V Javě jsou touto surovinou jednotlivé objekty nebo kolekce objektů. Operacemi je pak zpracování, třídění a balení. Celou výrobní linkou v tomto přirovnání je pak samotný stream.
Stream se skládá z:
- Počátečního zdroje dat
- Mezioperací
- Terminální operace
- Konečného výstupního výsledku
Pojďme se podívat na některé specifické vlastnosti streamů v Javě:
- Stream není datová struktura, která by byla uložena v paměti. Jedná se spíše o posloupnost prvků (polí, objektů nebo kolekcí objektů), která je manipulována pomocí specifických metod.
- Streamy jsou deklarativní, což znamená, že definujete, co má být provedeno, ale ne jak toho dosáhnout.
- Stream lze využít pouze jednou, jelikož data nejsou nikde uložena.
- Stream nemění původní datovou strukturu. Vytváří pouze novou strukturu odvozenou z původní.
- Výsledkem je hodnota získaná z terminální metody streamu.
Stream API vs. zpracování kolekcí
Kolekce je datová struktura uložená v paměti, která slouží k uchovávání a zpracování dat. Kolekce nabízí datové struktury jako jsou množiny, mapy, seznamy atd. pro ukládání dat. Naopak, stream představuje efektivní způsob, jak přenášet data během jejich zpracování.
Příklad použití ArrayList kolekce:
import java.util.ArrayList; public class Main { public static void main(String[] args) { ArrayList list = new ArrayList(); list.add(0, 3); System.out.println(list); } } Výstup: [3]
Jak je vidět v příkladu, lze vytvořit ArrayList, vložit do něj data a poté s nimi pracovat pomocí různých metod.
Pomocí streamů lze pracovat s existujícími datovými strukturami a generovat nové, upravené hodnoty. Následuje příklad vytvoření ArrayList a jeho filtrování pomocí streamu.
import java.util.ArrayList; import java.util.stream.Stream; public class Main { public static void main(String[] args) { ArrayList<Integer> list = new ArrayList(); for (int i = 0; i < 20; i++) { list.add(i+1); } System.out.println(list); Stream<Integer> filtered = list.stream().filter(num -> num > 10); filtered.forEach(num -> System.out.println(num + " ")); } } #Výstup [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20] 11 12 13 14 15 16 17 18 19 20
V uvedeném příkladu je stream vytvořen z existujícího seznamu, který je následně iterován pro filtrování hodnot větších než 10. Stream neukládá data, pouze prochází seznam a vypisuje výsledek. Pokud se pokusíte vytisknout samotný stream, dostanete namísto hodnot referenci na stream.
Použití Java Stream API
Java Stream API přijímá zdrojovou kolekci prvků nebo sekvenci prvků a následně provádí operace pro získání konečného výsledku. Stream si lze představit jako potrubí, kterým prochází sekvence prvků a je určitým způsobem transformována.
Stream lze vytvořit z různých zdrojů, například:
- Z kolekcí, jako je seznam nebo množina
- Z polí
- Ze souborů a jejich cest pomocí bufferu
V rámci streamu se provádí dva typy operací:
- Mezioperace
- Terminální operace
Mezioperace vs. terminální operace
Každá mezioperace vrací nový stream, který transformuje vstup pomocí specifikované metody. Žádný průchod daty se však fakticky neprovádí. Místo toho se data pouze předávají do dalšího streamu. K samotnému průchodu daty dochází až v momentě, kdy se zavolá terminální operace.
Například, pokud máte seznam 10 čísel, které chcete filtrovat a následně mapovat, nebudou všechny prvky procházeny okamžitě. Nejprve se zkontroluje, zda prvek splňuje podmínku pro filtraci a poté se v případě shody aplikuje mapování. Pro každý prvek vznikají nové streamy.
Operace mapování se tedy aplikuje pouze na ty prvky, které splnily podmínku pro filtraci, nikoli na celý seznam. K samotnému průchodu a spojení do jednoho výsledku dojde až během terminální operace.
Po provedení terminální operace je stream spotřebován a nelze jej dále použít. Pro opětovné provedení operací je nutné vytvořit nový stream.
Zdroj: The Bored Dev
Nyní, když máte základní přehled o tom, jak streamy fungují, podíváme se na implementaci streamů v Javě v detailu.
#1. Prázdný stream
Prázdný stream lze vytvořit pomocí metody `empty()` rozhraní Stream API.
import java.util.stream.Stream; public class Main { public static void main(String[] args) { Stream emptyStream = Stream.empty(); System.out.println(emptyStream.count()); } } Výstup: 0
V tomto příkladu, pokud vypíšeme počet prvků v prázdném streamu, dostaneme 0, jelikož neobsahuje žádné prvky. Prázdné streamy se hodí pro předcházení výjimkám typu `NullPointerException`.
#2. Stream z kolekcí
Kolekce jako seznamy a množiny poskytují metodu `stream()`, pomocí které lze z kolekce vytvořit stream. Následně je možné streamem procházet a získat konečný výsledek.
ArrayList<Integer> list = new ArrayList(); for (int i = 0; i < 20; i++) { list.add(i+1); } System.out.println(list); Stream<Integer> filtered = list.stream().filter(num -> num > 10); filtered.forEach(num -> System.out.println(num + " ")); #Výstup [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20] 11 12 13 14 15 16 17 18 19 20
#3. Stream z polí
Pro vytvoření streamu z pole slouží metoda `Arrays.stream()`.
import java.util.Arrays; public class Main { public static void main(String[] args) { String[] stringArray = new String[]{"this", "is", "etechblog.cz"}; Arrays.stream(stringArray).forEach(item -> System.out.print(item + " ")); } } #Výstup this is etechblog.cz
Je také možné specifikovat počáteční a koncový index prvků, ze kterých chceme stream vytvořit. Počáteční index je inkluzivní, zatímco koncový index je exkluzivní.
String[] stringArray = new String[]{"this", "is", "etechblog.cz"}; Arrays.stream(stringArray, 1, 3).forEach(item -> System.out.print(item + " ")); Výstup: is etechblog.cz
#4. Hledání minimálních a maximálních hodnot pomocí streamů
Přístup k maximální a minimální hodnotě v kolekci nebo poli lze provést pomocí komparátorů v Javě. Metody `min()` a `max()` přijímají komparátor a vracejí objekt typu `Optional`.
Objekt typu `Optional` je kontejner, který může, ale nemusí obsahovat nenulovou hodnotu. Pokud obsahuje nenulovou hodnotu, volání metody `get()` na tomto objektu vrátí hodnotu.
import java.util.Arrays; import java.util.Optional; public class MinMax { public static void main(String[] args) { Integer[] numbers = new Integer[]{21, 82, 41, 9, 62, 3, 11}; Optional<Integer> maxValue = Arrays.stream(numbers).max(Integer::compare); System.out.println(maxValue.get()); Optional<Integer> minValue = Arrays.stream(numbers).min(Integer::compare); System.out.println(minValue.get()); } } #Výstup 82 3
Užitečné zdroje
Nyní, když máte základní znalosti o streamech v Javě, zde je 5 zdrojů, které vám pomohou se zdokonalit v Javě 8:
#1. Java 8 v akci
Tato kniha je průvodcem novými funkcemi Javy 8, včetně streamů, lambd a funkčního programování. Kniha obsahuje také kvízy a otázky, které vám pomohou zkontrolovat vaše znalosti.
Knihu lze zakoupit v brožované vazbě i jako audioknihu na Amazonu.
#2. Java 8 Lambdas: Funkční programování pro masy
Tato kniha je zaměřena na vývojáře Javy SE a vysvětluje, jak přidání lambda výrazů ovlivňuje jazyk Java. Obsahuje srozumitelná vysvětlení, cvičení a příklady, které vám pomohou zvládnout lambda výrazy v Javě 8.
Kniha je dostupná v brožované vazbě a jako Kindle verze na Amazonu.
#3. Java SE 8 pro netrpělivé
Pokud jste zkušený vývojář Javy SE, tato kniha vás seznámí s vylepšeními Javy SE 8, Stream API, přidáním lambda výrazů, vylepšeními souběžného programování a také s některými funkcemi Javy 7, které většina vývojářů nezná.
Kniha je dostupná pouze v brožované vazbě na Amazonu.
#4. Naučte se funkční programování v Javě s lambdami a streamy
Tento kurz na Udemy se zaměřuje na základy funkčního programování v Javě 8 a 9. Kurz se věnuje lambda výrazům, metodám referencí, streamům a funkčním rozhraním.
Obsahuje také mnoho úloh a cvičení zaměřených na funkční programování.
#5. Knihovna tříd Javy
Java Class Library je součástí specializace Core Java, kterou nabízí Coursera. Naučíte se psát typově bezpečný kód s využitím Java Generics, porozumíte knihovně tříd s více než 4000 třídami, naučíte se pracovat se soubory a ošetřovat běhové chyby. Pro absolvování tohoto kurzu je však vyžadována znalost základů:
- Úvod do Javy
- Úvod do objektově orientovaného programování v Javě
- Objektově orientované hierarchie v Javě
Závěrem
Java Stream API a zavedení lambda výrazů v Javě 8 zjednodušilo a vylepšilo mnoho aspektů Javy, jako je paralelní iterace, funkční rozhraní, redukce kódu atd.
Streamy mají ale i svá omezení. Jedním z největších je, že je lze použít pouze jednou. Pokud jste vývojář Javy, výše uvedené zdroje vám mohou pomoci porozumět těmto konceptům mnohem podrobněji. Nezapomeňte si je tedy prohlédnout.
Mohlo by vás také zajímat téma zpracování výjimek v Javě.