Programovanie

Funkčné programovanie pre vývojárov Java, 2. časť

Vitajte späť v tomto dvojdielnom výučbe, ktorý predstavuje funkčné programovanie v kontexte Java. V časti Funkčné programovanie pre vývojárov Java, 1. časť, som pomocou príkladov JavaScriptu začal používať päť techník funkčného programovania: čisté funkcie, funkcie vyššieho rádu, lenivé hodnotenie, uzávierky a kari. Prezentácia týchto príkladov v JavaScripte nám umožnila sústrediť sa na techniky v jednoduchšej syntaxi bez toho, aby sme sa dostali k zložitejším funkčným programovacím schopnostiam Javy.

V časti 2 sa vrátime k týmto technikám pomocou kódu Java, ktorý predchádza jazyku Java 8. Ako uvidíte, tento kód je funkčný, ale nie je ľahké ho písať ani čítať. Tiež vás oboznámia s novými funkčnými programovacími funkciami, ktoré boli v jazyku Java 8 plne integrované do jazyka Java; a to lambdas, odkazy na metódy, funkčné rozhrania a Streams API.

V celom tomto výučbe sa vrátime k príkladom z 1. časti, aby sme zistili, ako si porovnávajú príklady JavaScriptu a Javy. Uvidíte tiež, čo sa stane, keď aktualizujem niektoré príklady z obdobia pred Java 8 s funkciami funkčného jazyka, ako sú lambdas a odkazy na metódy. Nakoniec tento tutoriál obsahuje praktické cvičenie, ktoré vám pomôže precvičujte funkčné myslenie, čo urobíte transformáciou časti objektovo orientovaného kódu Java na jeho funkčný ekvivalent.

stiahnuť Získajte kód Stiahnite si zdrojový kód napríklad pre aplikácie v tejto príručke. Vytvoril Jeff Friesen pre JavaWorld.

Funkčné programovanie v prostredí Java

Mnoho vývojárov si to neuvedomuje, ale bolo možné písať funkčné programy v Jave skôr ako Java 8. Aby sme mali prehľad o funkčnom programovaní v Jave, rýchlo si prečítajme funkcie funkčného programovania, ktoré predchádzali Jave 8. Akonáhle Mám ich dole, budete pravdepodobne viac oceňovať to, ako nové funkcie zavedené v prostredí Java 8 (ako lambdas a funkčné rozhrania) zjednodušili prístup Javy k funkčnému programovaniu.

Limity podpory Java pre funkčné programovanie

Aj napriek vylepšeniam funkčného programovania v prostredí Java 8 zostáva Java nevyhnutným objektovo orientovaným programovacím jazykom. Chýbajú mu typy rozsahov a ďalšie funkcie, vďaka ktorým by bol funkčnejší. Java je tiež obťažovaný nominatívnym písaním, čo je podmienka, že každý typ musí mať meno. Napriek týmto obmedzeniam majú vývojári, ktorí využívajú funkčné vlastnosti Javy, stále výhodu v tom, že môžu písať stručnejšie, opakovane použiteľné a čitateľné kódy.

Funkčné programovanie pred Java 8

Anonymné vnútorné triedy spolu s rozhraniami a uzávermi sú tri staršie funkcie, ktoré podporujú funkčné programovanie v starších verziách Javy:

  • Anonymné vnútorné triedy vám umožní odovzdať funkčnosť (opísanú rozhraniami) metódam.
  • Funkčné rozhrania sú rozhrania, ktoré popisujú funkciu.
  • Uzávery vám umožní prístup k premenným v ich vonkajšom rozsahu.

V nasledujúcich častiach sa znovu pozrieme na päť techník zavedených v časti 1, ale používajúcich syntax Java. Uvidíte, ako bola každá z týchto funkčných techník možná pred Java 8.

Písanie čistých funkcií v prostredí Java

Výpis 1 predstavuje zdrojový kód ukážkovej aplikácie, DaysInMonth, ktorý je napísaný pomocou anonymnej vnútornej triedy a funkčného rozhrania. Táto aplikácia ukazuje, ako napísať čistú funkciu, ktorá bola v Jave dosiahnuteľná dávno pred Javou 8.

Zoznam 1. Čistá funkcia v Jave (DaysInMonth.java)

funkcia rozhrania {R apply (T t); } verejná trieda 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} [mesiac]; }}; System.out.printf ("Apríl:% d% n", dim.apply (3)); System.out.printf ("August:% d% n", dim.apply (7)); }}

Generické Funkcia rozhranie v zozname 1 popisuje funkciu s jediným parametrom typu T a návratový typ typu R. The Funkcia rozhranie deklaruje R platí (T t) metóda, ktorá použije túto funkciu na daný argument.

The hlavný() metóda inštancuje anonymnú vnútornú triedu, ktorá implementuje Funkcia rozhranie. The použiť () metóda unboxes mesiac a používa ho na indexovanie poľa celých dní dní v mesiaci. Vráti sa celé číslo v tomto indexe. (Pre zjednodušenie ignorujem priestupné roky.)

hlavný() next vykoná túto funkciu dvakrát vyvolaním použiť () vrátiť počet dní za mesiace apríl a august. Tieto počty sa následne vytlačia.

Podarilo sa nám vytvoriť funkciu a čistú funkciu! Pripomeňme, že a čistá funkcia záleží len na jeho argumentoch a nijakom externom stave. Neexistujú žiadne vedľajšie účinky.

Zostavte zoznam 1 takto:

javac DaysInMonth.java

Výslednú aplikáciu spustite nasledujúcim spôsobom:

java DaysInMonth

Mali by ste dodržiavať nasledujúci výstup:

Apríl: 30. augusta: 31

Písanie funkcií vyššieho rádu v jazyku Java

Ďalej sa pozrieme na funkcie vyššieho rádu, známe tiež ako prvotriedne funkcie. Pamätajte, že a funkcia vyššieho rádu prijme argumenty funkcie a / alebo vráti výsledok funkcie. Java spája funkciu s metódou, ktorá je definovaná v anonymnej vnútornej triede. Inštancia tejto triedy sa odovzdáva alebo vracia z inej metódy Java, ktorá slúži ako funkcia vyššieho rádu. Nasledujúci fragment kódu orientovaný na súbor demonštruje odovzdanie funkcie funkcii vyššieho rádu:

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

Tento fragment kódu odovzdáva funkciu založenú na java.io.FileFilter funkčné rozhranie k java.io.Súbor triedy File [] listFiles (filter FileFilter) metóda, ktorá má povedať, že má vrátiť iba tie súbory, ktoré majú TXT rozšírenia.

Výpis 2 zobrazuje ďalší spôsob práce s funkciami vyššieho rádu v jazyku Java. V takom prípade kód odovzdá komparačnú funkciu a sort () funkcia vyššieho rádu pre triedenie vzostupne a druhá komparátorná funkcia do sort () pre triedenie v zostupnom poradí.

Zoznam 2. Funkcia vyššieho rádu v Jave (Sort.java)

import java.util.Comparator; verejná trieda Zoradiť {public static void main (String [] args) {String [] innerplanets = {"Merkúr", "Venuša", "Zem", "Mars"}; skládka (vnútorné planéty); sort (innerplanets, new Comparator () {@Override public int compare (String e1, String e2) {return e1.compareTo (e2);}}); skládka (vnútorné planéty); sort (innerplanets, new Comparator () {@Override public int compare (String e1, String e2) {return e2.compareTo (e1);}}); skládka (vnútorné planéty); } výpis statického voidu (pole T []) {pre (prvok T: pole) System.out.println (prvok); System.out.println (); } static void sort (pole T [], komparátor cmp) {pre (int pass = 0; pass  prejsť; i--) if (cmp.compare (pole [i], pole [prejsť]) <0) vymeniť (pole, i, prejsť); } swap statických void (T [] pole, int i, int j) {T temp = pole [i]; pole [i] = pole [j]; pole [j] = teplota; }}

Zoznam 2 importuje java.util.Comparator funkčné rozhranie, ktoré popisuje funkciu, ktorá môže vykonať porovnanie na dvoch objektoch ľubovoľného, ​​ale identického typu.

Dve významné časti tohto kódu sú: sort () metóda (ktorá implementuje algoritmus Bubble Sort) a metódu sort () vyvolania v hlavný() metóda. Hoci sort () zďaleka nie je funkčný, demonštruje funkciu vyššieho rádu, ktorá ako argument dostane funkciu - komparátor. Túto funkciu vykonáva vyvolaním svojej porovnať () metóda. Dve inštancie tejto funkcie sú odovzdané v dvoch sort () zavolá hlavný().

Zostavte zoznam 2 nasledovne:

javac Sort.java

Výslednú aplikáciu spustite nasledujúcim spôsobom:

java triediť

Mali by ste dodržiavať nasledujúci výstup:

Merkúr Venuša Zem Mars Mars Zem Mars Merkúr Venuša Venuša Merkúr Mars Earth

Lenivé hodnotenie v Jave

Lenivé hodnotenie je ďalšia funkčná programovacia technika, ktorá pre Java 8. nie je nová. Táto technika odďaľuje hodnotenie výrazu, kým nie je potrebná jeho hodnota. Vo väčšine prípadov Java dychtivo vyhodnocuje výraz, ktorý je viazaný na premennú. Java podporuje lenivé hodnotenie pre nasledujúcu konkrétnu syntax:

  • Booleovský && a || operátory, ktoré nebudú hodnotiť ich pravý operand, keď je ľavý operand nepravdivý (&&) alebo pravda (||).
  • The ?: operátor, ktorý vyhodnotí booleovský výraz a následne vyhodnotí iba jeden z dvoch alternatívnych výrazov (kompatibilného typu) na základe hodnoty true / false booleovského výrazu.

Funkčné programovanie podporuje programovanie orientované na výrazy, takže sa budete chcieť čo najviac vyhnúť používaniu príkazov. Predpokladajme napríklad, že chcete nahradiť Java ak-inak vyhlásenie s ifThenElse () metóda. Zoznam 3 zobrazuje prvý pokus.

Zoznam 3. Príklad dychtivého vyhodnotenia v Jave (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, štvorec (4), kocka (4))); } statická int kocka (int x) {System.out.println ("v kocke"); návrat x * x * x; } static int ifThenElse (boolovský predikát, int onTrue, int onFalse) {návrat (predikát)? onTrue: onFalse; } static int square (int x) {System.out.println ("in square"); návrat x * x; }}

Zoznam 3 definuje prílohu ifThenElse () metóda, ktorá vezme booleovský predikát a pár celých čísel a vráti znak pravda celé číslo, keď je predikát pravda a onFalse celé číslo inak.

Zoznam 3 tiež definuje kocka () a námestie() metódy. Respektíve tieto metódy kockujú a umocňujú celé číslo a vrátia výsledok.

The hlavný() metóda vyvoláva ifThenElse (true, square (4), cube (4)), ktoré by sa mali dovolávať iba štvorec (4), nasledovaný ifThenElse (false, square (4), cube (4)), ktoré by sa mali dovolávať iba kocka (4).

Zostavte zoznam 3 nasledovne:

javac EagerEval.java

Výslednú aplikáciu spustite nasledujúcim spôsobom:

java EagerEval

Mali by ste dodržiavať nasledujúci výstup:

na štvorci v kocke 16 na štvorci v kocke 64

Výstup ukazuje, že každý ifThenElse () volanie má za následok vykonanie oboch metód, bez ohľadu na boolovský výraz. Nemôžeme využiť ?: lenivosť operátora, pretože Java horlivo vyhodnocuje argumenty metódy.

Aj keď neexistuje spôsob, ako sa vyhnúť nedočkavému hodnoteniu argumentov metód, stále môžeme využívať výhody ?:lenivé hodnotenie, aby sa zabezpečilo, že iba námestie() alebo kocka () sa volá. Výpis 4 ukazuje, ako.

Výpis 4. Príklad lenivého vyhodnotenia v Jave (LazyEval.java)

funkcia rozhrania {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; }}; Funkcia kocka = new Function () {{System.out.println ("CUBE"); } @Override public Integer apply (Integer t) {System.out.println ("v kocke"); 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 otočení ifThenElse () do funkcie vyššieho rádu vyhlásením tejto metódy za príjem dvojice Funkcia argumenty. Aj keď sa tieto argumenty horlivo vyhodnocujú, keď sa dostanú na ifThenElse (), ?: operátor spôsobí vykonanie iba jednej z týchto funkcií (cez použiť ()). Pri zostavovaní a spustení aplikácie môžete vidieť horlivé aj lenivé hodnotenie v práci.

Zostavte zoznam 4 nasledovne:

javac LazyEval.java

Výslednú aplikáciu spustite nasledujúcim spôsobom:

java LazyEval

Mali by ste dodržiavať nasledujúci výstup:

ŠTVORČOVÁ KOCKA na štvorci 16 v kocke 64

Lenivý iterátor a ďalšie

„Lenivosť, časť 1: Skúmanie lenivého hodnotenia v Jave“ od Neala Forda poskytuje viac informácií o lenivom hodnotení. Autor predstavuje lenivý iterátor založený na prostredí Java spolu s niekoľkými lenivo orientovanými rámcami Java.

Uzávery v Jave

Anonymná inštancia vnútornej triedy je spojená s a uzáver. Musia sa deklarovať premenné vonkajšieho rozsahu konečné alebo (od Java 8) efektívne konečné (čo znamená, že po inicializácii neupravené), aby boli prístupné. Zvážte zoznam 5.

Zoznam 5. Príklad zatvorenia v prostredí Java (PartialAdd.java)

funkcia rozhrania {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 čiastočnýAdd; } public static void main (String [] args) {PartialAdd pa = new PartialAdd (); Funkcia add10 = pa.add (10); Funkcia add20 = pa.add (20); System.out.println (add10.apply (5)); System.out.println (add20.apply (5)); }}

Zoznam 5 je ekvivalentom Java uzávierky, ktorú som predtým predstavil v JavaScripte (pozri časť 1, zoznam 8). Tento kód vyhlasuje pridať () funkcia vyššieho rádu, ktorá vracia funkciu na vykonávanie čiastočnej aplikácie súboru pridať () funkcia. The použiť () premenná metódy prístupu X vo vonkajšom rozsahu pridať (), ktoré je potrebné deklarovať konečné pred Java 8. Kód sa chová skoro rovnako ako ekvivalent JavaScriptu.

Zostavte zoznam 5 takto:

javac PartialAdd.java

Výslednú aplikáciu spustite nasledujúcim spôsobom:

java PartialAdd

Mali by ste dodržiavať nasledujúci výstup:

15 25

Kari v Jave

Možno ste si všimli, že PartialAdd v zozname 5 demonštruje viac než len uzávierky. To tiež demonštruje kari, čo je spôsob, ako previesť hodnotenie funkcie s viacerými argumentmi na hodnotenie ekvivalentnej postupnosti funkcií s jedným argumentom. Oboje pa.add (10) a pa.add (20) v zozname 5 vráťte uzávierku, ktorá zaznamenáva operand (10 alebo 20, respektíve) a funkcia, ktorá vykonáva sčítanie - druhý operand (5) sa prechádza cez add10.apply (5) alebo add20.apply (5).

Currying nám umožňuje vyhodnocovať argumenty funkcií po jednom, pričom v každom kroku vytvoríme novú funkciu s jedným argumentom menej. Napríklad v PartialAdd aplikáciu, pripravujeme nasledujúcu funkciu:

f (x, y) = x + y

Mohli by sme použiť oba argumenty súčasne, čím by sme získali nasledujúce:

f (10, 5) = 10 + 5

Pri kari však použijeme iba prvý argument, ktorý prinesie tento:

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

Teraz máme jednu funkciu, g, stačí iba jediný argument. Toto je funkcia, ktorá sa vyhodnotí, keď zavoláme použiť () metóda.

Čiastočné uplatnenie, nie čiastočné doplnenie

Názov PartialAdd znamenať čiastočné uplatnenie z pridať () funkcia. Nestojí to za čiastočné doplnenie. Currying je o vykonaní čiastočnej aplikácie funkcie. Nejde o vykonávanie čiastkových výpočtov.

Možno by ste boli zmätení z môjho použitia výrazu „čiastočné uplatnenie“, najmä preto, že som v časti 1 uviedol, že kari nie je to isté ako čiastočné uplatnenie, čo je proces stanovenia množstva argumentov na funkciu, ktorý vytvára ďalšiu funkciu s menšou arititou. Pri čiastočnej aplikácii môžete vytvárať funkcie s viac ako jedným argumentom, ale pri použití kari musí mať každá funkcia práve jeden argument.

Zoznam 5 predstavuje malý príklad kari založeného na prostredí Java pred jazykom Java 8. Teraz zvážte CurriedCalc aplikácia v zozname 6.

Zoznam 6. Currying v kóde Java (CurriedCalc.java)

funkcia rozhrania {R apply (T t); } verejná trieda CurriedCalc {public static void main (String [] args) {System.out.println (calc (1) .apply (2) .apply (3) .apply (4)); } statická funkcia> calc (koncové celé číslo a) {návrat novej funkcie> () {@Override verejná funkcia použiť (posledné celé číslo b) {vrátiť novú funkciu() {@Override public Function apply (final Integer c) {return new Function () {@Override public Integer apply (Integer d) {return (a + b) * (c + d); }}; }}; }}; }}

Zoznam 6 používa na vyhodnotenie funkcie kari f (a, b, c, d) = (a + b) * (c + d). Daný výraz vypočítať (1). použiť (2). použiť (3). použiť (4), je táto funkcia zostavená nasledovne:

  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)

Zostaviť zoznam 6:

javac CurriedCalc.java

Spustite výslednú aplikáciu:

java CurriedCalc

Mali by ste dodržiavať nasledujúci výstup:

21

Pretože kari je o vykonaní čiastočnej aplikácie funkcie, nezáleží na tom, v akom poradí sa argumenty použijú. Napríklad namiesto míňania a do calc () a d k najviac vnoreným použiť () metódou (ktorá vykonáva výpočet) by sme mohli tieto názvy parametrov obrátiť. To by malo za následok d c b a namiesto a B C d, ale stále by dosiahlo rovnaký výsledok 21. (Zdrojový kód tohto tutoriálu obsahuje alternatívnu verziu servera CurriedCalc.)

Funkčné programovanie v prostredí Java 8

Funkčné programovanie pred Java 8 nie je pekné. Na vytvorenie, odovzdanie funkcie alebo vrátenie funkcie z prvotriednej funkcie je potrebné príliš veľa kódu. V predchádzajúcich verziách Javy tiež chýbajú preddefinované funkčné rozhrania a prvotriedne funkcie, ako napríklad filter a mapa.

Java 8 znižuje výrečnosť do značnej miery zavedením lambdas a odkazov na metódy v jazyku Java. Ponúka tiež preddefinované funkčné rozhrania a prostredníctvom rozhrania Streams API sprístupňuje filtrovanie, mapovanie, zmenšovanie a ďalšie opakovane použiteľné prvotriedne funkcie.

Na tieto vylepšenia sa pozrieme spoločne v ďalších častiach.

Písanie lambdas v kóde Java

A lambda je výraz, ktorý popisuje funkciu označením implementácie funkčného rozhrania. Tu je príklad:

() -> System.out.println ("moja prvá lambda")

Zľava doprava () identifikuje formálny zoznam parametrov lambda (neexistujú žiadne parametre), -> znamená výraz lambda a System.out.println ("moja prvá lambda") je telo lambda (kód, ktorý sa má vykonať).

Lambda má a typu, čo je akékoľvek funkčné rozhranie, pre ktoré je implementácia lambda. Jedným z takýchto typov je java.lang.Runnable, pretože Spustiteľnéje void run () metóda má tiež prázdny formálny zoznam parametrov:

Spustiteľný r = () -> System.out.println ("moja prvá lambda");

Môžete odovzdať lambdu kdekoľvek, kde a Spustiteľné je potrebný argument; napríklad Vlákno (spustiteľné r) konštruktér. Za predpokladu, že došlo k predchádzajúcemu zadaniu, môžete vyhovieť r tomuto konštruktorovi takto:

nový závit (r);

Prípadne môžete lambdu odovzdať priamo konštruktorovi:

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

Toto je určite kompaktnejšie ako pred Java 8:

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

Filtr súborov založený na lambde

Moja predchádzajúca ukážka funkcií vyššieho rádu predstavila súborový filter založený na anonymnej vnútornej triede. Tu je ekvivalent založený na lambde:

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

Vrátiť príkazy vo výrazoch lambda

V časti 1 som spomenul, že funkčné programovacie jazyky pracujú s výrazmi na rozdiel od príkazov. Pred jazykom Java 8 ste mohli vo veľkej miere vylúčiť príkazy vo funkčnom programovaní, ale nedokázali ste vylúčiť návrat vyhlásenie.

Vyššie uvedený fragment kódu ukazuje, že lambda nevyžaduje návrat príkaz na vrátenie hodnoty (v tomto prípade boolovská hodnota true / false): stačí zadať výraz bez návrat [a pridať] bodkočiarku. Pre lambdy s viacerými príkazmi však budete stále potrebovať návrat vyhlásenie. V týchto prípadoch musíte vložiť telo lambdy medzi zátvorky nasledovne (nezabudnite na ukončenie príkazu bodkočiarkou):

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

Lambdy s funkčnými rozhraniami

Mám ďalšie dva príklady na ilustráciu stručnosti lambdas. Najskôr sa vráťme k hlavný() metóda z Triediť aplikácia uvedená v zozname 2:

public static void main (String [] args) {String [] innerplanets = {"Merkúr", "Venuša", "Zem", "Mars"}; skládka (vnútorné planéty); triediť (vnútorné planéty, (e1, e2) -> e1.compareTo (e2)); skládka (vnútorné planéty); triediť (vnútorné planéty, (e1, e2) -> e2.compareTo (e1)); skládka (vnútorné planéty); }

Môžeme tiež aktualizovať calc () metóda z CurriedCalc aplikácia uvedená v zozname 6:

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

Spustiteľné, FileFiltera Komparátor sú príklady funkčné rozhrania, ktoré popisujú funkcie. Java 8 formalizoval tento koncept tým, že vyžadoval anotáciu funkčného rozhrania s java.lang.Funkčné rozhranie typ anotácie, ako v @Funkčné rozhranie. Rozhranie, ktoré je anotované týmto typom, musí deklarovať presne jednu abstraktnú metódu.

Môžete použiť preddefinované funkčné rozhrania Java (o ktorých sa bude diskutovať neskôr), alebo môžete ľahko určiť svoje vlastné, a to nasledovne:

@ Funkčné rozhranie rozhranie Funkcia {R apply (T t); }

Potom môžete použiť toto funkčné rozhranie, ako je to znázornené tu:

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 (funkcia f, int x) {návrat f.apply (x); }

Začínate s lambdami?

Ak ste v lambdách nováčikom, na pochopenie týchto príkladov budete pravdepodobne potrebovať viac informácií. V takom prípade si pozrite moje ďalšie predstavenie lambdas a funkčných rozhraní v časti „Začíname s výrazmi lambda v Jave“. Nájdete tiež množstvo užitočných blogových príspevkov o tejto téme. Jedným z príkladov je „Funkčné programovanie s funkciami Java 8“, v ktorom autor Edwin Dalorzo ukazuje, ako používať výrazy lambda a anonymné funkcie v prostredí Java 8.

Architektúra lambda

Každá lambda je nakoniec inštanciou nejakej triedy, ktorá sa generuje v zákulisí. Preskúmajte nasledujúce zdroje a získajte viac informácií o architektúre lambda:

  • „Ako fungujú lambdy a anonymné vnútorné triedy“ (Martin Farrell, DZone)
  • „Lambdas in Java: a Peek under the hood“ (Brian Goetz, GOTO)
  • "Prečo sú lambdy Java 8 vyvolané pomocou invokedynamic?" (Pretečenie zásobníka)

Myslím, že videoprezentácia Java Language Architect Briana Goetza o tom, čo sa deje pod kapotou s lambdami, je obzvlášť fascinujúca.

Odkazy na metódy v prostredí Java

Niektoré lambdy sa odvolávajú iba na existujúcu metódu. Napríklad vyvoláva nasledujúca lambda System.outje neplatné výtlačky metóda na jediný argument lambda:

(Reťazce) -> System.out.println (s)

Lambda predstavuje (Reťazec s) ako jeho formálny zoznam parametrov a telo kódu, ktorého System.out.println (s) výrazové výtlačky shodnota pre štandardný výstupný tok.

Ak chcete ušetriť stlačenie klávesu, môžete lambdu nahradiť znakom odkaz na metódu, čo je kompaktný odkaz na existujúcu metódu. Môžete napríklad nahradiť predchádzajúci fragment kódu nasledujúcim:

System.out :: println

Tu, :: znamená to System.outje void println (String s) metóda sa odkazuje. Výsledkom referencie metódy je oveľa kratší kód, ako sme dosiahli pri predchádzajúcej lambde.

Odkaz na metódu pre Sort

Predtým som ukázal verziu Triediť aplikácia zo zoznamu 2. Tu je napísaný rovnaký kód s odkazom na metódu:

public static void main (String [] args) {String [] innerplanets = {"Merkúr", "Venuša", "Zem", "Mars"}; skládka (vnútorné planéty); sort (innerplanets, String :: compareTo); skládka (vnútorné planéty); sort (innerplanets, Comparator.comparing (String :: toString) .reversed ()); skládka (vnútorné planéty); }

The Reťazec :: compareTo referenčná verzia metódy je kratšia ako verzia lambda (e1, e2) -> e1.compareTo (e2). Upozorňujeme však, že na vytvorenie ekvivalentného triedenia v opačnom poradí, ktoré obsahuje aj odkaz na metódu, je potrebný dlhší výraz: String :: toString. Namiesto konkretizácie String :: toString, Mohol som uviesť ekvivalent s -> s.toString () lambda.

Viac informácií o metódach

Odkazov na metódy je oveľa viac, ako by som v obmedzenom priestore dokázal pokryť. Ak sa chcete dozvedieť viac, pozrite si môj úvod do písania odkazov na metódy pre statické metódy, nestatické metódy a konštruktory v časti „Začíname s odkazmi na metódy v Jave“.

Preddefinované funkčné rozhrania

Java 8 predstavila preddefinované funkčné rozhrania (java.util.funkcia), aby vývojári nemuseli vytvárať vlastné funkčné rozhrania pre bežné úlohy. Tu je niekoľko príkladov:

  • The Spotrebiteľ Funkčné rozhranie predstavuje operáciu, ktorá prijíma jeden vstupný argument a nevracia žiadny výsledok. Jeho neplatnosť prijať (T t) metóda vykonáva túto operáciu na základe argumentu t.
  • The Funkcia Funkčné rozhranie predstavuje funkciu, ktorá prijíma jeden argument a vracia výsledok. Jeho R platí (T t) metóda použije túto funkciu na argument t a vráti výsledok.
  • The Predikát funkčné rozhranie predstavuje a predikát (Funkcia s boolovskou hodnotou) jedného argumentu. Jeho boolovský test (T t) metóda vyhodnotí tento predikát na základe argumentu t a vráti hodnotu true alebo false.
  • The Dodávateľ funkčné rozhranie predstavuje dodávateľa výsledkov. Jeho Získať () metóda nedostane žiadny argument, ale vráti výsledok.

The DaysInMonth aplikácia v zozname 1 odhalila úplnosť Funkcia rozhranie. Počnúc jazykom Java 8 môžete toto rozhranie odstrániť a importovať identické preddefinované Funkcia rozhranie.

Viac informácií o preddefinovaných funkčných rozhraniach

"Začíname s výrazmi lambda v jazyku Java" poskytuje príklady Spotrebiteľ a Predikát funkčné rozhrania. Pozrite sa na príspevok na blogu "Java 8 - Vyhodnocovanie lenivých argumentov" a nájdite zaujímavé použitie pre Dodávateľ.

Okrem toho, hoci sú preddefinované funkčné rozhrania užitočné, prinášajú aj určité problémy. Bloger Pierre-Yves Saumont vysvetľuje prečo.

Funkčné API: prúdy

Java 8 predstavila rozhranie Streams API na uľahčenie postupného a paralelného spracovania údajových položiek. Toto API je založené na potoky, kde Prúd je postupnosť prvkov pochádzajúca zo zdroja a podporujúca postupné a paralelné agregačné operácie. A zdroj ukladá prvky (napríklad kolekciu) alebo generuje prvky (napríklad generátor náhodných čísel). An agregát je výsledok vypočítaný z viacerých vstupných hodnôt.

Stream podporuje prechodné a terminálne operácie. An medzioperácia vráti nový stream, zatiaľ čo a terminálna prevádzka spotrebúva prúd. Prevádzky sú spojené do a potrubie (prostredníctvom reťazenia metód). Potrubie začína zdrojom, za ktorým nasleduje nula alebo viac medzioperácií, a končí operáciou terminálu.

Prúdy sú príkladom a funkčné API. Ponúka filtračné, mapové, zmenšovacie a ďalšie opakovane použiteľné prvotriedne funkcie. Krátko som toto API demonštroval v Zamestnanci aplikácia uvedená v časti 1, Zoznam 1. Zoznam 7 ponúka ďalší príklad.

Výpis 7. Funkčné programovanie pomocou prúdov (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 (); Reťazec [] mestá = {"New York", "Londýn", "Paríž", "Berlín", "Brazília", "Tokio", "Peking", "Jeruzalem", "Káhira", "Rijád", "Moskva" }; IntStream.range (0, 11) .mapToObj (i -> mestá [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ý() metóda najskôr vytvorí prúd pseudonáhodných celých čísel začínajúcich na 0 a končiacich 10. Prúd je obmedzený na presne 10 celých čísel. The filter () prvotriedna funkcia dostane lambdu ako svoj predikátový argument. Predikát odstráni nepárne celé čísla z toku. Nakoniec pre každý() prvotriedna funkcia vytlačí každé párne celé číslo na štandardný výstup cez System.out :: println odkaz na metódu.

The hlavný() metóda ďalej vytvorí celočíselný prúd, ktorý vytvorí postupný rozsah celých čísel začínajúcich na 0 a končiacich 10. mapToObj () funkcia prvej triedy dostane lambdu, ktorá mapuje celé číslo na ekvivalentný reťazec v celočíselnom indexe v Mestá pole. Názov mesta sa potom pošle na štandardný výstup cez server pre každý() prvotriedna funkcia a jej System.out :: println odkaz na metódu.

Nakoniec hlavný() demonštruje znížiť () prvotriedna funkcia. Celočíselný prúd, ktorý vytvára rovnaký rozsah celých čísel ako v predchádzajúcom príklade, sa zníži na súčet ich hodnôt, ktorý sa následne vydá.

Identifikácia prechodných a terminálnych operácií

Každý z limit (), filter (), rozsah ()a mapToObj () sú prechodné operácie, zatiaľ čo pre každý() a znížiť () sú terminálne operácie.

Zostavte zoznam 7 takto:

javac StreamFP.java

Výslednú aplikáciu spustite nasledujúcim spôsobom:

java StreamFP

Z jedného behu som pozoroval nasledujúci výstup:

0 2 10 6 0 8 10 New York Londýn Paríž Berlín Brazília Tokio Peking Jeruzalem Káhira Rijád Moskva 45 45

Možno by ste čakali 10 namiesto 7 pseudonáhodných párnych celých čísel (v rozmedzí od 0 do 10, vďaka rozsah (0, 11)) sa objaví na začiatku výstupu. Po všetkom, limit (10) Zdá sa, že naznačuje, že na výstup bude 10 celých čísel. To však nie je tento prípad. Napriek tomu limit (10) výsledkom hovoru je prúd presne 10 celých čísel, filter (x -> x% 2 == 0) výsledkom hovoru budú nepárne celé čísla odstránené zo streamu.

Viac informácií o streamoch

Ak nie ste oboznámení s prúdmi, pozrite si môj návod predstavujúci nové rozhranie Streams API Java SE 8, kde nájdete ďalšie informácie o tomto funkčnom rozhraní API.

Na záver

Mnoho vývojárov Java nebude pokračovať v čisto funkčnom programovaní v jazyku ako Haskell, pretože sa tak veľmi líši od známej imperatívnej objektovo orientovanej paradigmy. Funkčné programovacie schopnosti Java 8 sú určené na prekonanie tejto medzery a umožňujú vývojárom v Java písať kód, ktorý je ľahšie pochopiteľný, udržiavateľný a testovateľný. Funkčný kód je tiež viac opakovane použiteľný a vhodnejší na paralelné spracovanie v Jave. So všetkými týmito stimulmi nie je naozaj dôvod, aby ste do svojho Java kódu nezačlenili možnosti funkčného programovania Java.

Napíšte funkčnú aplikáciu Bubble Sort

Funkčné myslenie je termín, ktorý vytvoril Neal Ford a ktorý sa týka kognitívneho posunu od objektovo orientovanej paradigmy k paradigme funkčného programovania. Ako ste videli v tejto príručke, je možné sa veľa naučiť o funkčnom programovaní prepísaním objektovo orientovaného kódu pomocou funkčných techník.

Uzavrite to, čo ste sa doteraz naučili, opätovnou návštevou aplikácie Triedenie v zozname 2. V tomto rýchlom tipe vám ukážem, ako na to napíš čisto funkčný Bubble Sort, najskôr pomocou techník pred Java 8 a potom pomocou funkčných funkcií Java 8.

Tento príbeh, „Funkčné programovanie pre vývojárov Java, 2. časť“, bol pôvodne publikovaný spoločnosťou JavaWorld.

$config[zx-auto] not found$config[zx-overlay] not found