Příklad Třídění osob - řešení
Jelikož úloha se skládá z více částí, pro každou část je vhodné vytvořit si jednu pomocnou metodu. V ukázce řešení jsou všechny takovéto metody bez návratové hodnoty (každá z nich totiž výsledek rovnou vypisuje do konzole) a mají jeden parametr, kterým je výchozí seznam osob.
V první části je nejprve nutné převést seznam typu List<Osoba>
na datový proud. Poté je ovšem potřeba prvky vyfiltrovat tak, aby v datovém proudu byly jen ty osoby, jejichž jména začínají na P. Toho lze dosáhnout pomocí metody filter()
. Tato metoda má jako parametr instanci funkčního rozhraní Predicate<T>
. Jeho instanci lze získat pomocí lambda výrazu, který je možné dosadit rovnou jako parametr do metody filter()
. Podmínkou je, že musí vracet hodnotu boolean. Tak lze získat pouze ty prvky, které odpovídají dané podmínce a poté už jen stačí zajistit výstup těchto prvků zpět do seznamu pomocí metody collect()
. Jak tato metoda následně vypadá, je znázorněno na ukázce níže.
public void vypsatPJmena(List<Osoba> seznam) {
System.out.println("Osoby s jménem na P:");
List<Osoba> seznamP = seznam.stream()
.filter(p -> p.getJmeno().startsWith("P"))
.collect(Collectors.toList());
System.out.println(seznamP);
}
Druhá část úlohy se může na první pohled zdát složitější. Jedná se totiž o práci s mapou. Výsledná mapa má mít věk osob jako klíč a hodnotou je pak seznam osob s tímto věkem. Mapa tedy bude deklarována jako Map<Integer, List<Osoba>>
. Nejprve je opět nutné převést zdrojový seznam na datový proud. Jelikož úkolem je vypsat všechny prvky seznamu bez jakýchkoli úprav, úloha spočívá pouze v tom převést seznam na mapu.
K tomu poslouží metoda collect()
, která má jako parametr instanci rozhraní Collector<T,A,R>
. Pro její získání je vhodné použít některou z metod třídy Collectors
. Jelikož úkolem je seskupit osoby dle hodnot atributu věk, nejjednodušším řešením bude použít metodu metodu grouppingBy()
. Jako její parametr opět poslouží lambda výraz, který zajistí získání instance funkčního rozhraní Function<T,R>
. Zde jen stačí určit, že se má seskupovat dle věku. Výslednou mapu pak lze jednoduše vypsat do konzole pomocí metody rozhraní Iterable forEach()
. Výše popsaná implementace takto vzniklé pomocné metody je zobrazena níže.
public void vypsatMapuDleVeku(List<Osoba> seznam) {
System.out.println("Mapa osob dle věku:");
Map<Integer, List<Osoba>> mapaVek = seznam.stream().collect(Collectors.groupingBy(p -> p.getVek()));
mapaVek.forEach((vek, osoba) -> System.out.format("věk %s: %s\n", vek, osoba));
}
Třetí část spočívá v zjištění průměrné výšky osob v seznamu. Opět je nutné vytvořit ze seznamu datový proud. Prvky není třeba nějak upravovat (jedná se pouze o průměr všech prvků), proto je možné rovnou zavolat metodu collect()
. Zde pro získání instance rozhraní Collector<T,A,R>
jako parametr této metody lze využít jednu z předpřipravených metod třídy Collectors
. Tato třída má tři metody, které vrací průměr: averagingDouble()
, averagingInt()
a averagingLong()
. Jelikož výška osob je uložená jako double, hodí se použít metodu averagingDouble()
. Jako její parametr se opět využije lambda výraz, který zde udává, z jakého atributu osoby se má udělat průměr. Takto implementovaná pomocná metoda vypadá následovně.
public void vypsatPrumernouVysku(List<Osoba> seznam) {
System.out.println("Průměrná výška:");
double prumernaVyska = seznam.stream().collect(Collectors.averagingDouble(p -> p.getVyska()));
System.out.println(prumernaVyska);
}
Poslední část se od těch přechozích liší. Oproti druhé a třetí části úlohy nejde pouze o to zvolit správnou metodu třídy Collectors
. Úkolem totiž není pracovat se všemi prvky zdrojového seznamu, tak jak jsou, tzn. typu Osoba
, ale pouze se jmény vybraných osob.
Opět se nejprve vytvoří ze seznamu datový proud. Ve vzniklém datovém proudu jsou ale všechny prvky seznamu. Úkolem je najít pouze ty, jejichž věk je vyšší než 60 let. K tomu slouží metoda filter()
. Jako parametr se jí předá lambda výraz implementující abstraktní metodu test()
tak, že bude vracet true, pokud je věk dané osoby větší než 60. Nově vzniklý datový proud tak obsahuje pouze 2 instance třídy Osoba
(Rudolf a Petra). Pokud bychom je teď vypsali do věty, věta by obsahovala všechny údaje o těchto dvou osobách, což odporuje zadání. Je nutné pracovat v datovém proudu pouze se jmény těchto osob. Potřebujeme tedy převést prvky na String obsahující pouze jméno. K tomu poslouží metoda map()
, kde se pomocí lambda výrazu určí, že prvky nově vzniklého datového proudu budou pouze jména osob. Teď už je možné zavolat metodu collect()
.
Pro získání instance rozhraní Collector<T,A,R>
lze opět použít již předpřipravené řešení v podobě zavolání metody třídy Collectors joining(CharSequence oddelovac, CharSequence prefix, CharSequence suffix)
. Tato metoda umožní seskládat větu ze zadání: „Osoby x a y jsou starší 60-ti let“. Jako oddělovač je zde uvedeno „a“. Prefixem je ve větě slovo „Osoby“ a sufixem je pak „jsou starší 60-ti let“. O doplnění jmen se postará metoda sama. Výsledná pomocná metoda je ukázána na výpise níže.
public void vypsatSpojeni(List<Osoba> seznam) {
System.out.println("Spojování:");
String veta = seznam.stream()
.filter(p -> p.getVek() > 60)
.map(p -> p.getJmeno())
.collect(Collectors.joining(" a ", "Osoby ", " jsou starší 60-ti let."));
System.out.println(veta);
}
Celkové řešení je v projektu Třídění osob - řešení.