Moje odkazy
vydáno: 17. 1. 2015 23:23, aktualizováno: 9. 3. 2015 00:11
Dnes si na praktických příkladech ukážeme další novinky v Javě 8. Lambda výrazy, kterými jsme se zabývali minule, tu nejsou jenom pro okrasu – používají se ve standardní knihovně a umožňují elegantní práci s proudy objektů skrze nové Stream API.
Nejedná se o nízkoúrovňové vstupně-výstupní proudy (InputStream
atd.) přenášející bajty, ale o objektové proudy generického typu, pomocí kterých pracujeme s daty na vyšší úrovni abstrakce. Pojďme se rovnou podívat na příklady.
Pro začátek získáme instanci Stream<T>
z obyčejné kolekce. Projdeme všechny prvky a něco s nimi provedeme:
Collection<String> písmena = Arrays.asList(new String[]{"a", "b", "c"});
Stream<String> proud = písmena.stream();
proud.forEach(p -> System.out.println(p)); // p je typu String
Proud samozřejmě nemusíme přiřazovat do proměnné a můžeme zavolat forEach()
rovnou, ale takhle je vidět datový typ, se kterým pracujeme. Ostatně: i když některé ukázky můžou na první pohled vypadat trochu magicky, žádná magie v tom není – vždy pracujeme s konkrétním datovým typem, instancí nějaké třídy (rozhraní), můžeme se podívat na JavaDoc a IDE nám bude napovídat metody. Pokud by něco nebylo jednoznačné, zdroják v Javě nepůjde zkompilovat.
Metoda forEach()
přijímá jako argument instanci funkčního rozhraní Consumer<>
. Tu jsme si v tomto případě vytvořili ad-hoc pomocí lambda výrazu, ale můžeme klidně využít i předem připravenou instanci v proměnné nebo se odkázat pomocí čtyřtečky na metodu nějaké třídy či objektu:
proud.forEach(System.out::println);
Další věc, kterou můžeme s proudy dělat jsou transformace – přemapujeme jednu hodnotu na jinou pomocí funkce. Např. změníme velikost písmen a nakonec zase vypíšeme na standardní výstup:
písmena.stream()
.map(p -> p.toUpperCase())
.forEach(s -> System.out.println(s));
Metoda map()
nemění původní proud, ale vrací nám nový – ten může být i jiného typu – záleží na tom, co vrací daná funkce. Takto převedeme Stream<String>
na Stream<Integer>
:
Collection<String> pozdravy = Arrays.asList(new String[]{
"ahoj", "nazdar", "dobrý den"});
pozdravy.stream()
.map(s -> s.length())
.forEach(i -> System.out.println("délka řetězce: " + i));
Proudy můžeme filtrovat – datový typ se v tomto případě nemění – jen vybíráme určitou podmnožinu prvků na základě predikátu:
Collection<Integer> n = Arrays.asList(new Integer[]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9});
n.stream().filter(i -> i % 2 == 0).forEach(i -> System.out.println("sudé: " + i));
Predikát je instance funkčního rozhraní Predicate<>
a opět ji můžeme zadat funkcí nebo se na ni odkázat. Predikát přijímá na vstupu daný generický typ (nebo předka) a vrací logickou hodnotu.
Dalšími operacemi nad proudy jsou reduce()
a collect()
. Ty se hodí na součty a obecně agregační funkce. Prostý součet čísel:
int součet = n.stream().reduce((a, b) -> a + b).get();
Součet délek řetězců:
int d = pozdravy.stream().collect(Collectors.summingInt((String s) -> s.length()));
System.out.println("Celková délka pozdravů: " + d);
Proudy nemusí být odvozené jen z kolekcí – můžeme je získat i jiných zdrojů a můžeme si je i sami generovat a postavit na nich nějaké vlastní API.
Např. proud Stream<Path>
cest/souborů pocházející ze souborového systému – vyhledáme soubory s příponou .pdf
a určitou velikostí:
Files.list(Paths.get("/tmp/"))
.filter(p -> p.toString().endsWith(".pdf") && p.toFile().length() > 1024)
.forEach(p -> System.out.println("Soubor: " + p));
Proudy se zpracovávají skutečně proudově-průběžně – můžeme mít i nekonečný proud dat, např. náhodná čísla. V tom případě se nám hodí metoda limit()
, kterou omezíme počet zpracovávaných prvků:
Random r = new Random();
r.ints().limit(10).forEach(i -> System.out.println("náhodné číslo: " + i));
Nové proudové API v Javě 8 umožňuje psát více deklarativně – kód pak může připomínat spíše SQL (projekce, restrikce, agregační funkce…) než procedurální jazyky.
Témata: [softwarové inženýrství] [softwarová architektura] [Java]
Tento článek zatím nikdo nekomentoval