O programování 03 - Přehlednost funkcionálního zápisu v Java 8
V předchozím díle jsem zjistil, že výkonnost streamů, resp. funkcionálního zápisu není tak špatná, jak se na první pohled zdálo, spíše naopak. Nicméně hlavním argumentem pro zavedení funkcionálního přístupu do Java 8 je větší čitelnost kódu. V této oblasti však osobně docela narážím, protože mi použité řešení nepřipadá dobré.
Vrátím se k příkladu z minulého dílu. Použití klasického cyklu foreach není nijak zázračný zápis, ale na první pohled chápu, co se děje - iteruji přes celý seznam a do výstupního seznamu přidávám prvky zvětšené o jedna.
for (Integer value: inputList) {
outputList.add(++value);
}
Přímočarý přepis (jak ho nabízí NetBeansy) s využitím funkcionálních rysů vypadá takto:
inputList.stream().forEach((value) -> {
outputList.add(++value);
});
Zdánlivě je to skoro totéž, přesto se mi výsledek prostě nelíbí. Na listu volám volám jakousi metodu stream() a pak foreach(), následuje šipka a za ní kombinace různých závorek. Šipka a závorky jsou jen syntaktický cukr, možná ne úplně přehledný, ale zásadní je otázka - proč zrovna stream(), zvlášť, když existuje i metoda foreach() přímo na listu a kromě stream() je ještě k dispozici paralelStream(). To už je docela guláš, jednu věc mohu napsat třemi způsoby...
Samozřejmě, že si lze vše nastudovat v dokumentaci a použít optimální variantu, ale podle mě to ztrácí kouzlo funkcionálního přístupu, protože se (už zase) zabývám tím JAK, místo abych (jenom) popsal CO.
Hlavně se mi ale nelíbí to, že stream() je běžná metoda a ne klíčové slovo a tím pádem nejde o rys jazyka. Důvodem může být to, že díky tomu přistupuje kompilátor i runtime k funkcionálním rysům stejně jako k ostatním metodám, což má výhodu jednodušší implementace.
Nové klíčové slovo by se mi líbilo kvůli možnosti zvýraznění syntaxe v IDE - takto na první pohled nepoznám, že používám funkcionální rysy jazyka a mohu přehlédnout třeba to, že proměnná v lambda výrazu musí být effectively final. To samozřejmě platí i pro inner class pro nějž je lambda defakto syntaktický cukr ale i tak, je to prostě potenciální místo pro chybu. A hodně "přitaženě" za vlasy - tvůrci jazyka mohou metodu stream() v budoucnu označit jako deprecated a tím odstřihnout funkcionální rysy z Javy.
Ovšem i po takto krátké úvaze chápu, co vedlo tvůrce k tomu nezavádět úplně novou syntaxi ale využít stávající možnosti jazyka jak jen to šlo ale nadšen z toho nejsem.
***
Vezměme si o něco složitější případ, v němž chceme sečíst pole čísel. Pomiňme, že bych na to čekal knihovní funkci (a možná i taková existuje), přímočaře v Javě bych to napsal třeba takto:
Integer result = 0;
for (Integer value : list) {
result += value;
}
Opět vidím na první pohled, co kód dělá, ale když ho přepíšu funkcionálně, např. takto, jsem zmaten.
Integer result = list.stream().map((value) -> value).reduce(0, Integer::sum);
Super je, že je zápis na jednom řádku, ale co to sakra znamená? V zápisu se objevují různé "tajemné" metody jako map() a reduce(), o Integer::sum ani nemluvě. Zkrátka musím při čtení tohoto kousku kódu trochu přemýšlet, abych pochopil, co vlastně dělá, přitom jenom sčítám čísla a přitom používám 4 různé metody (stream(), map(), reduce(), sum()). Osobně by se mi prostě líbila nějaká elegantnější syntaxe.
Aktualizace 15. 12. 2016
V diskusi jsem byl upozorněn, že příklad na součet lze zapsat bez matoucí (a zbytečné) metody map takto:
Integer result = list.stream().reduce(0, Integer::sum);
Můj původní kód mi vygenerovaly NetBeansy a bohužel jsem ho detailně nezkoumal. Tenhle zápis je mnohem přehlednější ale padá jím tak trochu původní pointa celého článku.
- O programování 01 - Úvod
- O programování 02 - Efektivita funkcionálního přístupu v Java 8
- O programování 04 - Java Microbenchmark Harness
- O programování 05 - Další varianty Javovské implementace foreach
- O programování 06 - Návrhové vzory - síla i slabina Javy
- O programování 07 - Funkce jako argument aneb od factory k lambdě v Javě
- O programování 08 - Jak v Javě předat metodu, která bude mít různé parametry
- O programování 09 - Pokročilé využití streamů a funkcionálního přístupu v Java 8
- O programování 10 - Java a inspirace v Ruby
- O programování 11 - Minimalistické programy a Java
- O programování 12 - Paralelní implementace násobení matic v Java - úvod