Programování

Funkční programování pro vývojáře Java, část 2

Vítejte zpět v tomto dvoudílném výukovém programu představujícím funkční programování v kontextu Java. Ve funkčním programování pro vývojáře Java, část 1, jsem použil příklady JavaScriptu, které vám pomohou začít s pěti technikami funkčního programování: čisté funkce, funkce vyššího řádu, líné vyhodnocení, uzávěry a kari. Prezentace těchto příkladů v JavaScriptu nám umožnila soustředit se na techniky v jednodušší syntaxi, aniž bychom se dostali ke složitějším funkčním programovacím schopnostem Javy.

V části 2 se vrátíme k těmto technikám pomocí kódu Java, který předchází prostředí Java 8. Jak uvidíte, tento kód je funkční, ale není snadné ho psát ani číst. Také vám budou představeny nové funkce funkčního programování, které byly plně integrovány do jazyka Java v prostředí Java 8; jmenovitě lambdas, odkazy na metody, funkční rozhraní a Streams API.

V celém tomto kurzu se znovu podíváme na příklady z části 1, abychom zjistili, jak se porovnávají příklady JavaScript a Java. Uvidíte také, co se stane, když aktualizuji některé příklady z doby před Java 8 o funkce funkčních jazyků, jako jsou lambdas a odkazy na metody. Nakonec tento výukový program obsahuje praktické cvičení, které vám pomůže procvičujte funkční myšlení, což provedete transformací části objektově orientovaného kódu Java na jeho funkční ekvivalent.

stáhnout Získat kód Stáhněte si zdrojový kód například pro aplikace v tomto výukovém programu. Vytvořil Jeff Friesen pro JavaWorld.

Funkční programování s Javou

Mnoho vývojářů si to neuvědomuje, ale bylo možné psát funkční programy v Javě před Java 8. Abychom měli ucelený přehled o funkčním programování v Javě, pojďme rychle zkontrolovat funkce funkčního programování, které předcházely Javě 8. Jakmile Pokud je máme, pravděpodobně si více uvědomíte, jak nové funkce zavedené v Javě 8 (jako lambdas a funkční rozhraní) zjednodušily přístup Javy k funkčnímu programování.

Omezení podpory Java pro funkční programování

I přes vylepšení funkčního programování v prostředí Java 8 zůstává Java imperativním objektově orientovaným programovacím jazykem. Chybí typy rozsahů a další funkce, díky nimž by byl funkčnější. Javu také trápí jmenované psaní, což je podmínka, že každý typ musí mít jméno. I přes tato omezení vývojáři, kteří využívají funkční funkce Javy, stále těží z možnosti psát stručnější, opakovaně použitelný a čitelný kód.

Funkční programování před Java 8

Anonymní vnitřní třídy spolu s rozhraními a uzávěrkami jsou tři starší funkce, které podporují funkční programování ve starších verzích prostředí Java:

  • Anonymní vnitřní třídy vám umožní předat funkčnost (popsanou rozhraními) metodám.
  • Funkční rozhraní jsou rozhraní, která popisují funkci.
  • Uzávěry vám umožní přístup k proměnným v jejich vnějších oborech.

V následujících částech se vrátíme k pěti technikám zavedeným v části 1, ale s použitím syntaxe Java. Uvidíte, jak byla každá z těchto funkčních technik před verzí Java 8 možná.

Psaní čistých funkcí v Javě

Výpis 1 představuje zdrojový kód ukázkové aplikace, DaysInMonth, který je psán pomocí anonymní vnitřní třídy a funkčního rozhraní. Tato aplikace ukazuje, jak napsat čistou funkci, která byla dosažitelná v Javě dlouho před Javou 8.

Výpis 1. Čistá funkce v Javě (DaysInMonth.java)

funkce rozhraní {R apply (T t); } veřejná třída DaysInMonth {public static void main (String [] args) {Function dim = new Function () {@Override public Integer apply (Integer month) {return new Integer [] {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31} [měsíc]; }}; System.out.printf ("Duben:% d% n", dim.apply (3)); System.out.printf ("Srpen:% d% n", dim.apply (7)); }}

Obecné Funkce Rozhraní v seznamu 1 popisuje funkci s jediným parametrem typu T a návratový typ typu R. The Funkce rozhraní deklaruje R platí (T t) metoda, která použije tuto funkci na daný argument.

The hlavní() metoda vytvoří instanci anonymní vnitřní třídy, která implementuje Funkce rozhraní. The aplikovat() metoda unboxes Měsíc a používá jej k indexování řady celých dnů od měsíce. Celé číslo v tomto indexu je vráceno. (Pro zjednodušení ignoruji přestupné roky.)

hlavní() next provede tuto funkci dvakrát vyvoláním aplikovat() vrátit počet dní za měsíce duben a srpen. Tyto počty jsou následně vytištěny.

Podařilo se nám vytvořit funkci a čistou funkci! Připomeňme, že a čistá funkce záleží pouze na jeho argumentech a na žádném externím stavu. Neexistují žádné vedlejší účinky.

Kompilace výpisu 1 následovně:

javac DaysInMonth.java

Výslednou aplikaci spusťte následovně:

java DaysInMonth

Měli byste dodržovat následující výstup:

Duben: 30. srpna: 31

Psaní funkcí vyššího řádu v Javě

Dále se podíváme na funkce vyššího řádu, známé také jako funkce první třídy. Pamatujte, že a funkce vyššího řádu přijímá argumenty funkce a / nebo vrací výsledek funkce. Java přidruží funkci k metodě, která je definována v anonymní vnitřní třídě. Instance této třídy je předána nebo vrácena z jiné metody Java, která slouží jako funkce vyššího řádu. Následující fragment kódu orientovaný na soubor ukazuje předání funkce funkci vyššího řádu:

File [] txtFiles = new File ("."). ListFiles (new FileFilter () {@Override public boolean accept (File pathname) {return pathname.getAbsolutePath (). EndsWith ("txt");}});

Tento fragment kódu předává funkci založenou na java.io.FileFilter funkční rozhraní k java.io.soubor třídy Soubor [] listFiles (filtr FileFilter) metoda, která říká, že má vracet pouze ty soubory s txt rozšíření.

Výpis 2 ukazuje další způsob práce s funkcemi vyššího řádu v Javě. V tomto případě kód předá funkci komparátoru a sort () funkce vyššího řádu pro vzestupné řazení a druhá komparační funkce do sort () pro řazení v sestupném pořadí.

Výpis 2. Funkce vyššího řádu v Javě (Sort.java)

import java.util.Comparator; public class Sort {public static void main (String [] args) {String [] innerplanets = {"Merkur", "Venuše", "Země", "Mars"}; skládka (vnitřní planety); sort (innerplanets, new Comparator () {@Override public int compare (String e1, String e2) {return e1.compareTo (e2);}}); skládka (vnitřní planety); sort (innerplanets, new Comparator () {@Override public int compare (String e1, String e2) {return e2.compareTo (e1);}}); skládka (vnitřní planety); } výpis statických void (pole T []) {pro (prvek T: pole) System.out.println (prvek); System.out.println (); } static void sort (T [] array, Comparator cmp) {for (int pass = 0; pass  složit; i--) if (cmp.compare (array [i], array [pass]) <0) swap (array, i, pass); } swap statických void (T [] pole, int i, int j) {T temp = pole [i]; pole [i] = pole [j]; pole [j] = teplota; }}

Výpis 2 importuje soubor java.util.Comparator funkční rozhraní, které popisuje funkci, která může provádět srovnání na dvou objektech libovolného, ​​ale identického typu.

Dvě významné části tohoto kódu jsou sort () metoda (která implementuje algoritmus Bubble Sort) a sort () vyvolání v hlavní() metoda. Ačkoli sort () zdaleka není funkční, ukazuje funkci vyššího řádu, která jako argument přijímá funkci - komparátor. Vykonává tuto funkci vyvoláním jeho porovnat () metoda. Dvě instance této funkce jsou předány ve dvou sort () zavolá hlavní().

Sestavte výpis 2 následujícím způsobem:

javac Sort.java

Výslednou aplikaci spusťte následovně:

java řazení

Měli byste dodržovat následující výstup:

Merkur Venuše Země Mars Země Mars Merkur Venuše Venuše Merkur Mars Země

Líné vyhodnocení v Javě

Líné hodnocení je další funkční programovací technika, která není pro Java 8. nová. Tato technika odkládá vyhodnocení výrazu, dokud není potřeba jeho hodnota. Ve většině případů Java dychtivě vyhodnotí výraz, který je vázán na proměnnou. Java podporuje líné hodnocení pro následující konkrétní syntaxi:

  • Booleovský && a || operátory, které nebudou hodnotit jejich pravý operand, když je levý operand nepravdivý (&&) nebo true (||).
  • The ?: operátor, který vyhodnotí logický výraz a následně vyhodnotí pouze jeden ze dvou alternativních výrazů (kompatibilního typu) na základě hodnoty true / false logického výrazu.

Funkční programování podporuje programování zaměřené na výrazy, takže se budete chtít co nejvíce vyhnout používání příkazů. Předpokládejme například, že chcete nahradit prostředí Java -li-jiný prohlášení s Jestliže pak jinak() metoda. Výpis 3 zobrazuje první pokus.

Výpis 3. Příklad dychtivého vyhodnocení v Javě (EagerEval.java)

public class EagerEval {public static void main (String [] args) {System.out.printf ("% d% n", ifThenElse (true, square (4), cube (4))); System.out.printf ("% d% n", ifThenElse (false, square (4), krychle (4))); } statická int krychle (int x) {System.out.println ("v krychli"); návrat x * x * x; } static int ifThenElse (boolean predicate, int onTrue, int onFalse) {return (predicate)? onTrue: onFalse; } static int square (int x) {System.out.println ("in square"); návrat x * x; }}

Výpis 3 definuje Jestliže pak jinak() metoda, která bere booleovský predikát a dvojici celých čísel a vrací pravda celé číslo, když je predikát skutečný a onFalse jinak celé číslo.

Výpis 3 také definuje krychle() a náměstí() metody. Respektive tyto metody krychle a kvadratura celé číslo a vrátit výsledek.

The hlavní() metoda vyvolá ifThenElse (true, square (4), krychle (4)), který by se měl dovolávat pouze čtverec (4), následován ifThenElse (false, square (4), krychle (4)), který by se měl dovolávat pouze krychle (4).

Sestavte výpis 3 následujícím způsobem:

javac EagerEval.java

Výslednou aplikaci spusťte následovně:

java EagerEval

Měli byste dodržovat následující výstup:

ve čtverci v krychli 16 ve čtverci v krychli 64

Výstup ukazuje, že každý Jestliže pak jinak() Výsledkem volání je provedení obou metod bez ohledu na logický výraz. Nemůžeme využít ?: lenost operátora, protože Java dychtivě vyhodnocuje argumenty metody.

I když neexistuje způsob, jak se vyhnout nedočkavému vyhodnocení argumentů metody, stále můžeme využít výhody ?:je líné hodnocení zajistit, že pouze náměstí() nebo krychle() je nazýván. Výpis 4 ukazuje, jak.

Výpis 4. Příklad líného vyhodnocení v Javě (LazyEval.java)

funkce rozhraní {R apply (T t); } public class LazyEval {public static void main (String [] args) {Function square = new Function () {{System.out.println ("SQUARE"); } @Override public Integer apply (Integer t) {System.out.println ("in square"); návrat t * t; }}; Function cube = new Function () {{System.out.println ("CUBE"); } @Override public Integer apply (Integer t) {System.out.println ("in cube"); návrat t * t * t; }}; System.out.printf ("% d% n", ifThenElse (true, square, cube, 4)); System.out.printf ("% d% n", ifThenElse (false, square, cube, 4)); } static R ifThenElse (boolean predicate, Function onTrue, Function onFalse, T t) {return (predicate? onTrue.apply (t): onFalse.apply (t)); }}

Výpis 4 tahů Jestliže pak jinak() do funkce vyššího řádu deklarováním této metody pro příjem dvojice Funkce argumenty. I když jsou tyto argumenty dychtivě vyhodnoceny, když jsou předány Jestliže pak jinak()?: operátor způsobí provedení pouze jedné z těchto funkcí (přes aplikovat()). Při kompilaci a spuštění aplikace můžete vidět jak dychtivé, tak líné hodnocení v práci.

Sestavte výpis 4 takto:

javac LazyEval.java

Výslednou aplikaci spusťte následovně:

java LazyEval

Měli byste dodržovat následující výstup:

SQUARE CUBE ve čtverci 16 v krychli 64

Líný iterátor a další

„Lenost, část 1: Zkoumání líného hodnocení v Javě“ Neala Forda poskytuje lepší přehled o líném hodnocení. Autor představuje líný iterátor založený na jazyce Java spolu s několika líně orientovanými rámci Java.

Uzávěry v Javě

Anonymní instance vnitřní třídy je přidružena k uzavření. Musí být deklarovány proměnné vnějšího rozsahu finále nebo (počínaje Java 8) efektivně konečné (tj. nezměněný po inicializaci), aby byl přístupný. Zvažte výpis 5.

Výpis 5. Příklad uzavření v Javě (PartialAdd.java)

funkce rozhraní {R apply (T t); } public class PartialAdd {Function add (final int x) {Function partialAdd = new Function () {@Override public Integer apply (Integer y) {return y + x; }}; návrat částečnýPřidat; } public static void main (String [] args) {PartialAdd pa = new PartialAdd (); Funkce add10 = pa.add (10); Funkce add20 = pa.add (20); System.out.println (add10.apply (5)); System.out.println (add20.apply (5)); }}

Výpis 5 je Java ekvivalent uzávěru, který jsem dříve představil v JavaScriptu (viz Část 1, Výpis 8). Tento kód deklaruje přidat() funkce vyššího řádu, která vrací funkci pro provádění částečné aplikace přidat() funkce. The aplikovat() metoda přistupuje k proměnné X ve vnějším rozsahu přidat(), který musí být deklarován finále před Java 8. Kód se chová téměř stejně jako ekvivalent JavaScriptu.

Sestavte výpis 5 následujícím způsobem:

javac PartialAdd.java

Výslednou aplikaci spusťte následovně:

java PartialAdd

Měli byste dodržovat následující výstup:

15 25

Kari v Javě

Možná jste si všimli, že Částečný v seznamu 5 ukazuje víc než jen uzávěrky. To také ukazuje kari, což je způsob, jak převést vyhodnocení funkce s více argumenty do vyhodnocení ekvivalentní posloupnosti funkcí s jedním argumentem. Oba pa.add (10) a pa.add (20) v seznamu 5 vraťte uzávěr, který zaznamenává operand (10 nebo 20, respektive) a funkce, která provádí sčítání - druhý operand (5) prochází přes add10.apply (5) nebo add20.apply (5).

Currying nám umožňuje vyhodnocovat argumenty funkcí jeden po druhém a vytvářet novou funkci s jedním argumentem méně v každém kroku. Například v Částečný aplikace, jsme currying následující funkce:

f (x, y) = x + y

Mohli bychom použít oba argumenty současně, což by přineslo následující:

f (10, 5) = 10 + 5

U kari však použijeme pouze první argument, čímž získáme tento:

f (10, y) = g (y) = 10 + y

Nyní máme jednu funkci, G, to vyžaduje pouze jediný argument. Toto je funkce, která bude vyhodnocena, když zavoláme aplikovat() metoda.

Částečná aplikace, nikoli částečné přidání

Název Částečný znamená částečná aplikace z přidat() funkce. Nestojí to za částečné přidání. Currying je o provádění částečné aplikace funkce. Nejde o provádění částečných výpočtů.

Mohlo by vás zmást mé použití výrazu „částečná aplikace“, zejména proto, že jsem v části 1 uvedl, že kari není stejné jako částečná aplikace, což je proces fixace řady argumentů na funkci, produkující další funkci menší arity. S částečnou aplikací můžete vytvářet funkce s více než jedním argumentem, ale s kari, každá funkce musí mít přesně jeden argument.

Výpis 5 představuje malý příklad kari na bázi Java před verzí Java 8. Nyní zvažte CurriedCalc aplikace v seznamu 6.

Výpis 6. Currying v kódu Java (CurriedCalc.java)

funkce rozhraní {R apply (T t); } veřejná třída CurriedCalc {public static void main (String [] args) {System.out.println (calc (1) .apply (2) .apply (3) .apply (4)); } statická funkce> calc (konečné celé číslo a) {návrat nové funkce> () {@Override veřejná funkce použít (poslední celé číslo b) {vrátit novou funkci() {@Override public Function apply (final Integer c) {return new Function () {@Override public Integer apply (Integer d) {return (a + b) * (c + d); }}; }}; }}; }}

Výpis 6 používá k vyhodnocení funkce kari f (a, b, c, d) = (a + b) * (c + d). Daný výraz calc (1). použít (2). použít (3). použít (4), je tato funkce kari takto:

  1. f (1, b, c, d) = g (b, c, d) = (1 + b) * (c + d)
  2. g (2, c, d) = h (c, d) = (1 + 2) * (c + d)
  3. h (3, d) = i (d) = (1 + 2) * (3 + d)
  4. i (4) = (1 + 2) * (3 + 4)

Zkompilovat výpis 6:

javac CurriedCalc.java

Spusťte výslednou aplikaci:

java CurriedCalc

Měli byste dodržovat následující výstup:

21

Protože kari je o provádění částečné aplikace funkce, nezáleží na tom, v jakém pořadí jsou argumenty použity. Například místo předávání A na calc () a d k nejvíce vnořeným aplikovat() metoda (která provádí výpočet), můžeme tyto názvy parametrů obrátit. To by mělo za následek d c b a namísto abeceda, ale stále by dosáhlo stejného výsledku 21. (Zdrojový kód tohoto kurzu obsahuje alternativní verzi CurriedCalc.)

Funkční programování v Javě 8

Funkční programování před Java 8 není hezké. K vytvoření, předání funkce nebo vrácení funkce z prvotřídní funkce je zapotřebí příliš mnoho kódu. Předchozí verze Javy také postrádají předdefinovaná funkční rozhraní a prvotřídní funkce, jako je filtr a mapa.

Java 8 snižuje výřečnost do značné míry zavedením lambd a odkazů na metody v jazyce Java. Nabízí také předdefinovaná funkční rozhraní a zpřístupňuje filtrování, mapování, zmenšování a další opakovaně použitelné prvotřídní funkce prostřednictvím rozhraní Streams API.

Na tato vylepšení se podíváme společně v následujících částech.

Psaní lambdas v kódu Java

A lambda je výraz, který popisuje funkci tím, že označuje implementaci funkčního rozhraní. Zde je příklad:

() -> System.out.println ("moje první lambda")

Zleva doprava, () identifikuje formální seznam parametrů lambda (neexistují žádné parametry), -> znamená výraz lambda a System.out.println ("moje první lambda") je tělo lambda (kód, který se má provést).

Lambda má typ, což je jakékoli funkční rozhraní, pro které je lambda implementací. Jeden takový typ je java.lang.Runnable, protože Spustitelnýje neplatný běh () metoda má také prázdný formální seznam parametrů:

Runnable r = () -> System.out.println ("moje první lambda");

Můžete předat lambdu kamkoli, kde a Spustitelný argument je vyžadován; například Vlákno (Runnable r) konstruktor. Za předpokladu, že došlo k předchozímu úkolu, můžete projít r tomuto konstruktoru takto:

new Thread (r);

Alternativně můžete předat lambdu přímo konstruktoru:

new Thread (() -> System.out.println ("my first lambda"));

To je rozhodně kompaktnější než verze před Java 8:

new Thread (new Runnable () {@Override public void run () {System.out.println ("my first lambda");}});

Filtr souborů založený na lambda

Moje předchozí demonstrace funkcí vyššího řádu představila filtr souborů založený na anonymní vnitřní třídě. Zde je ekvivalent založený na lambda:

Soubor [] txtFiles = nový soubor ("."). ListFiles (p -> p.getAbsolutePath (). EndsWith ("txt"));

Vrátit příkazy ve výrazech lambda

V části 1 jsem zmínil, že funkční programovací jazyky pracují s výrazy na rozdíl od příkazů. Před Java 8 jste mohli do značné míry eliminovat příkazy ve funkčním programování, ale nemohli jste eliminovat vrátit se prohlášení.

Výše uvedený fragment kódu ukazuje, že lambda nevyžaduje a vrátit se příkaz pro vrácení hodnoty (v tomto případě logická hodnota true / false): stačí zadat výraz bez vrátit se [a přidat] středník. Pro lambdas s více příkazy však budete stále potřebovat vrátit se prohlášení. V těchto případech musíte umístit tělo lambdy mezi závorky následujícím způsobem (nezapomeňte ukončit středník středníkem):

File [] txtFiles = new File ("."). ListFiles (p -> {return p.getAbsolutePath (). EndsWith ("txt");});

Lambdy s funkčními rozhraními

Mám další dva příklady pro ilustraci stručnosti lambdas. Nejprve se podívejme na hlavní() metoda z Třídit aplikace zobrazená v seznamu 2:

public static void main (String [] args) {String [] innerplanets = {"Merkur", "Venuše", "Země", "Mars"}; skládka (vnitřní planety); třídit (vnitřní planety, (e1, e2) -> e1.compareTo (e2)); skládka (vnitřní planety); třídit (vnitřní planety, (e1, e2) -> e2.compareTo (e1)); skládka (vnitřní planety); }

Můžeme také aktualizovat calc () metoda z CurriedCalc aplikace zobrazená v seznamu 6:

statická funkce> calc (Celé číslo a) {návrat b -> c -> d -> (a + b) * (c + d); }

Spustitelný, FileFilter, a Komparátor jsou příklady funkční rozhraní, které popisují funkce. Java 8 formuloval tento koncept tím, že vyžaduje, aby bylo funkční rozhraní opatřeno poznámkami s java.lang.FunctionalInterface typ poznámky, jako v @Funkčnírozhraní. Rozhraní, které je anotováno tímto typem, musí deklarovat přesně jednu abstraktní metodu.

Můžete použít předdefinovaná funkční rozhraní Java (o nichž bude pojednáno později), nebo můžete snadno určit vlastní, a to následujícím způsobem:

@FunctionalInterface Interface Function {R apply (T t); }

Potom můžete použít toto funkční rozhraní, jak je znázorněno zde:

public static void main (String [] args) {System.out.println (getValue (t -> (int) (Math.random () * t), 10)); System.out.println (getValue (x -> x * x, 20)); } statické celé číslo getValue (funkce f, int x) {návrat f.apply (x); }

Jste na lambdách noví?

Pokud jste v oboru lambdas noví, možná budete potřebovat více pozadí, abyste těmto příkladům porozuměli. V takovém případě se podívejte na můj další úvod do lambd a funkčních rozhraní v části „Začínáme s výrazy lambda v Javě“. Na toto téma také najdete řadu užitečných blogových příspěvků. Jedním z příkladů je „Funkční programování s funkcemi Java 8“, ve kterém autor Edwin Dalorzo ukazuje, jak používat výrazy lambda a anonymní funkce v prostředí Java 8.

Architektura lambda

Každá lambda je nakonec instancí nějaké třídy, která je generována v zákulisí. Prozkoumejte následující zdroje a dozvíte se více o architektuře lambda:

  • „Jak fungují lambda a anonymní vnitřní třídy“ (Martin Farrell, DZone)
  • „Lambdas in Java: a Peek under the hood“ (Brian Goetz, GOTO)
  • "Proč jsou lambdy Java 8 vyvolávány pomocí invokedynamic?" (Přetečení zásobníku)

Myslím, že videoprezentace Java Language Architect Briana Goetze o tom, co se děje pod kapotou s lambdami, je obzvláště fascinující.

Odkazy na metody v Javě

Některé lambdy vyvolávají pouze existující metodu. Například následující lambda vyvolá System.outje neplatný tisk (y) metoda na jediném argumentu lambda:

(String s) -> System.out.println (s)

Lambda představuje (Řetězce) jako jeho formální seznam parametrů a tělo kódu, jehož System.out.println (s) tisk výrazů shodnota pro standardní výstupní proud.

Chcete-li uložit stisknutí kláves, můžete lambdu nahradit a odkaz na metodu, což je kompaktní odkaz na existující metodu. Například můžete předchozí fragment kódu nahradit následujícím:

System.out :: println

Tady, :: znamená to System.outje void println (String s) metoda je odkazována. Výsledkem odkazu na metodu je mnohem kratší kód, než jsme dosáhli u předchozí lambda.

Odkaz na metodu pro Sort

Předtím jsem ukázal lambda verzi Třídit aplikace ze seznamu 2. Zde je stejný kód napsaný s odkazem na metodu:

public static void main (String [] args) {String [] innerplanets = {"Merkur", "Venuše", "Země", "Mars"}; skládka (vnitřní planety); sort (innerplanets, String :: compareTo); skládka (vnitřní planety); sort (innerplanets, Comparator.comparing (String :: toString) .reversed ()); skládka (vnitřní planety); }

The String :: compareTo referenční verze metody je kratší než verze lambda (e1, e2) -> e1.compareTo (e2). Všimněte si však, že pro vytvoření ekvivalentního řazení v opačném pořadí je vyžadován delší výraz, který také obsahuje odkaz na metodu: String :: toString. Místo upřesnění String :: toString, Mohl jsem specifikovat ekvivalent s -> s.toString () lambda.

Více informací o metodách

Odkazů na metody je mnohem víc, než bych dokázal pokrýt v omezeném prostoru. Chcete-li se dozvědět více, podívejte se na můj úvod do psaní odkazů na metody pro statické metody, nestatické metody a konstruktory v části „Začínáme s odkazy na metody v Javě“.

Předdefinovaná funkční rozhraní

Java 8 představila předdefinovaná funkční rozhraní (java.util.funkce), aby vývojáři nemuseli vytvářet vlastní funkční rozhraní pro běžné úkoly. Zde je několik příkladů:

  • The Spotřebitel funkční rozhraní představuje operaci, která přijímá jediný vstupní argument a nevrací žádný výsledek. Své neplatnost přijmout (T t) metoda provádí tuto operaci na argumentu t.
  • The Funkce Funkční rozhraní představuje funkci, která přijímá jeden argument a vrací výsledek. Své R platí (T t) metoda použije tuto funkci na argument t a vrátí výsledek.
  • The Predikát funkční rozhraní představuje a predikát (Funkce s booleovskou hodnotou) jednoho argumentu. Své booleovský test (T t) metoda vyhodnotí tento predikát na základě argumentu t a vrátí true nebo false.
  • The Dodavatel funkční rozhraní představuje dodavatele výsledků. Své T dostat () metoda nepřijímá žádné argumenty, ale vrací výsledek.

The DaysInMonth aplikace v seznamu 1 odhalila úplnost Funkce rozhraní. Počínaje Java 8 můžete toto rozhraní odebrat a importovat stejné předdefinované Funkce rozhraní.

Více o předdefinovaných funkčních rozhraních

"Začínáme s výrazy lambda v Javě" poskytuje příklady Spotřebitel a Predikát funkční rozhraní. Podívejte se na příspěvek na blogu „Java 8 - Lazy argument evaluation“ a objevte zajímavé použití pro Dodavatel.

I když jsou předdefinovaná funkční rozhraní užitečná, představují také některé problémy. Blogger Pierre-Yves Saumont vysvětluje proč.

Funkční API: streamy

Java 8 představila rozhraní Streams API k usnadnění sekvenčního a paralelního zpracování datových položek. Toto API je založeno na proudy, kde proud je posloupnost prvků pocházejících ze zdroje a podporujících sekvenční a paralelní agregační operace. A zdroj ukládá prvky (například kolekci) nebo generuje prvky (například generátor náhodných čísel). An agregát je výsledek vypočítaný z více vstupních hodnot.

Proud podporuje mezilehlé a terminální operace. An mezioperace vrací nový stream, zatímco a provoz terminálu spotřebovává proud. Provoz je propojen do a potrubí (prostřednictvím řetězení metod). Potrubí začíná zdrojem, za nímž následuje nula nebo více mezilehlých operací, a končí operací terminálu.

Streams je příkladem a funkční API. Nabízí filtrování, mapování, zmenšování a další opakovaně použitelné prvotřídní funkce. Krátce jsem předvedl toto API v Zaměstnanci aplikace uvedená v části 1, Výpis 1. Výpis 7 nabízí další příklad.

Výpis 7. Funkční programování pomocí streamů (StreamFP.java)

import java.util.Random; import java.util.stream.IntStream; public class StreamFP {public static void main (String [] args) {new Random (). ints (0, 11) .limit (10) .filter (x -> x% 2 == 0) .forEach (System.out :: println); System.out.println (); Řetězec [] města = {"New York", "Londýn", "Paříž", "Berlín", "Bras ,lia", "Tokio", "Peking", "Jeruzalém", "Káhira", "Rijád", "Moskva" }; IntStream.range (0, 11) .mapToObj (i -> města [i]) .forEach (System.out :: println); System.out.println (); System.out.println (IntStream.range (0, 10) .reduce (0, (x, y) -> x + y)); System.out.println (IntStream.range (0, 10) .reduce (0, Integer :: sum)); }}

The hlavní() metoda nejprve vytvoří proud pseudonáhodných celých čísel začínajících na 0 a končících na 10. Proud je omezen na přesně 10 celých čísel. The filtr() funkce první třídy přijímá lambdu jako svůj predikátový argument. Predikát odstraní z proudu lichá celá čísla. Nakonec pro každého() prvotřídní funkce vytiskne každé sudé celé číslo na standardní výstup pomocí System.out :: println odkaz na metodu.

The hlavní() metoda next vytvoří celočíselný proud, který vytvoří sekvenční rozsah celých čísel začínajících na 0 a končících na 10. The mapToObj () funkce první třídy přijímá lambdu, která mapuje celé číslo na ekvivalentní řetězec v celočíselném indexu v města pole. Název města je poté odeslán na standardní výstup prostřednictvím pro každého() prvotřídní funkce a její System.out :: println odkaz na metodu.

Nakonec hlavní() předvádí snížit() prvotřídní funkce. Celočíselný proud, který vytváří stejný rozsah celých čísel jako v předchozím příkladu, se sníží na součet jejich hodnot, který se následně odešle.

Identifikace mezilehlých a terminálových operací

Každý z omezit(), filtr(), rozsah(), a mapToObj () jsou mezilehlé operace, zatímco pro každého() a snížit() jsou terminální operace.

Sestavte výpis 7 následujícím způsobem:

javac StreamFP.java

Výslednou aplikaci spusťte následovně:

java StreamFP

Pozoroval jsem následující výstup z jednoho běhu:

0 2 10 6 0 8 10 New York Londýn Paříž Berlín BrasÌlia Tokio Peking Jeruzalém Káhira Rijád Moskva 45 45

Možná jste očekávali 10 namísto 7 pseudonáhodných sudých celých čísel (v rozmezí od 0 do 10, díky rozsah (0, 11)) se objeví na začátku výstupu. Po všem, limit (10) Zdá se, že naznačuje, že na výstupu bude 10 celých čísel. To však není tento případ. Ačkoliv limit (10) Výsledkem volání je proud přesně 10 celých čísel, filtr (x -> x% 2 == 0) volání má za následek odebrání lichých celých čísel ze streamu.

Více o streamech

Pokud nejste obeznámeni s Streams, podívejte se na můj tutoriál představující nové API Streams Java SE 8, kde najdete další informace o tomto funkčním API.

Závěrem

Mnoho vývojářů Java nebude usilovat o čisté funkční programování v jazyce, jako je Haskell, protože se tak výrazně liší od známého imperativního objektově orientovaného paradigmatu. Funkce funkčního programování v Java 8 jsou navrženy tak, aby tuto mezeru překlenuly, což vývojářům v Javě umožňuje psát kód, který je snadnější pochopit, udržovat a testovat. Funkční kód je také více použitelný a vhodnější pro paralelní zpracování v Javě. Se všemi těmito pobídkami není důvod, abyste do svého Java kódu nezačlenili možnosti funkčního programování Java.

Napište funkční aplikaci Bubble Sort

Funkční myšlení je termín vytvořený Nealem Fordem, který odkazuje na kognitivní posun od objektově orientovaného paradigmatu k paradigmatu funkčního programování. Jak jste viděli v tomto kurzu, je možné se hodně naučit o funkčním programování přepsáním objektově orientovaného kódu pomocí funkčních technik.

Uzavřete to, co jste se doposud dozvěděli, tím, že se vrátíte k aplikaci Třídění ze seznamu 2. V tomto rychlém tipu vám ukážu, jak na to napsat čistě funkční Bubble Sort, nejprve pomocí technik předcházejících Java 8 a poté pomocí funkčních funkcí Java 8.

Tento příběh, „Funkční programování pro vývojáře prostředí Java, část 2“, byl původně publikován společností JavaWorld.