Dobrodošli nazaj v tej dvodelni vadnici, ki predstavlja funkcionalno programiranje v kontekstu Jave. V Funkcionalnem programiranju za razvijalce Java, 1. del, sem z primeri JavaScript uporabil, da ste začeli s petimi tehnikami funkcionalnega programiranja: čiste funkcije, funkcije višjega reda, leno vrednotenje, zapiranje in curry. Predstavitev teh primerov v JavaScriptu nam je omogočila, da se osredotočimo na tehnike v enostavnejši sintaksi, ne da bi se spuščali v kompleksnejše funkcionalne programske zmožnosti Java.
V 2. delu bomo ponovno pregledali te tehnike z uporabo kode Java, ki je pred datumom Java 8. Kot boste videli, je ta koda funkcionalna, vendar je ni enostavno pisati ali brati. Prav tako se boste seznanili z novimi funkcijami funkcionalnega programiranja, ki so bile v celoti integrirane v jezik Java v Javi 8; in sicer lambde, sklice na metode, funkcionalne vmesnike in API Streams.
V tej vadnici bomo ponovno pregledali primere iz 1. dela, da bomo videli primerjavo primerov JavaScript in Java. Videli boste tudi, kaj se zgodi, ko posodobim nekatere primere pred Java 8 s funkcionalnimi jezikovnimi funkcijami, kot so lambda in sklici na metode. Nazadnje, ta vadnica vključuje tudi praktično vajo, ki vam je v pomoč vadite funkcionalno razmišljanje, kar boste storili s pretvorbo kosa objektno usmerjene kode Java v njen funkcionalni ekvivalent.
prenos Prenesite kodo Prenesite izvorno kodo, na primer programe v tej vadnici. Ustvaril Jeff Friesen za JavaWorld.Funkcionalno programiranje z Javo
Mnogi razvijalci se tega ne zavedajo, vendar je bilo mogoče pred Java 8 napisati funkcionalne programe v Javi 8. Da bi imeli dobro zaokrožen pogled na funkcionalno programiranje v Javi, na hitro preglejmo funkcije funkcionalnega programiranja, ki so pred Java 8. Ko enkrat Zmanjšali ste jih, verjetno boste bolj cenili, kako so nove funkcije, uvedene v Javi 8 (na primer lambde in funkcionalni vmesniki), poenostavile Javin pristop k funkcionalnemu programiranju.
Omejitve podpore Java za funkcionalno programiranje
Tudi z izboljšavami funkcionalnega programiranja v Javi 8 ostaja Java nujni objektno usmerjen programski jezik. Manjkajo tipi obsega in druge funkcije, zaradi katerih bi bil bolj funkcionalen. Java je omajana tudi z imenskim tipkanjem, ki določa, da mora imeti vsak tip ime. Kljub tem omejitvam imajo razvijalci, ki sprejemajo funkcionalne funkcije Jave, še vedno korist, da lahko napišejo bolj jedrnato, večkratno in berljivo kodo.
Funkcionalno programiranje pred Java 8
Anonimni notranji razredi, skupaj z vmesniki in zapirali, so tri starejše funkcije, ki podpirajo funkcionalno programiranje v starejših različicah Jave:
- Anonimni notranji razredi vam omogočajo, da funkcijo (opisano z vmesniki) prenesete na metode.
- Funkcionalni vmesniki so vmesniki, ki opisujejo funkcijo.
- Zapore vam omogoča dostop do spremenljivk v njihovih zunanjih obsegih.
V naslednjih razdelkih bomo ponovno pregledali pet tehnik, predstavljenih v 1. delu, vendar z uporabo sintakse Java. Videli boste, kako je bila vsaka od teh funkcionalnih tehnik mogoča pred Java 8.
Pisanje čistih funkcij v Javi
Seznam 1 predstavlja izvorno kodo za primer aplikacije, DaysInMonth
, ki je napisan z uporabo anonimnega notranjega razreda in funkcionalnega vmesnika. Ta aplikacija prikazuje, kako napisati čisto funkcijo, kar je bilo v Javi mogoče doseči že dolgo pred Java 8.
Seznam 1. Čista funkcija v Javi (DaysInMonth.java)
vmesniška funkcija {R velja (T t); } javni razred 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} [mesec]; }}; System.out.printf ("April:% d% n", dim.apply (3)); System.out.printf ("Avgust:% d% n", dim.apply (7)); }}
Splošno Funkcija
vmesnik v seznamu 1 opisuje funkcijo z enim samim parametrom tipa T
in vrnjeni tip tipa R
. The Funkcija
vmesnik razglasi R velja (T t)
metoda, ki uporablja to funkcijo za dani argument.
The glavni ()
metoda ustvari anonimni notranji razred, ki izvaja Funkcija
vmesnik. The uporabi ()
odstrani metodo mesec
in ga uporablja za indeksiranje vrste celih števil v dneh v mesecu. Vrne se celo število pri tem indeksu. (Prestavnih let zaradi preprostosti ne upoštevam.)
glavni ()
next to funkcijo izvede dvakrat s klicem uporabi ()
vrniti štetje dni za mesece april in avgust. Ta štetja se naknadno natisnejo.
Uspelo nam je ustvariti funkcijo in to čisto funkcijo! Spomnimo se, da a čista funkcija odvisen samo od njegovih argumentov in nobenega zunanjega stanja. Stranskih učinkov ni.
Sestavite seznam 1, kot sledi:
javac DaysInMonth.java
Nastalo aplikacijo zaženite na naslednji način:
java DaysInMonth
Upoštevati morate naslednje rezultate:
April: 30. avgust: 31.
Pisanje funkcij višjega reda v Javo
Nato si bomo ogledali funkcije višjega reda, znane tudi kot prvovrstne funkcije. Ne pozabite, da a funkcija višjega reda prejme argumente funkcije in / ali vrne rezultat funkcije. Java funkcijo poveže z metodo, ki je definirana v anonimnem notranjem razredu. Primer tega razreda se posreduje ali vrne iz druge metode Java, ki služi kot funkcija višjega reda. Naslednji datotečno usmerjeni fragment kode prikazuje posredovanje funkcije funkciji višjega reda:
Datoteka [] txtFiles = nova datoteka ("."). ListFiles (nova FileFilter () {@Override javno logično sprejeti (ime datoteke) {return pathname.getAbsolutePath (). ENDWith ("txt");}});
Ta fragment kode posreduje funkcijo, ki temelji na java.io.FileFilter
funkcijski vmesnik java.io.File
razredov File [] listFiles (filter FileFilter)
metoda, ki ji sporoča, naj vrne samo tiste datoteke z txt
razširitve.
Seznam 2 prikazuje še en način za delo s funkcijami višjega reda v Javi. V tem primeru koda posreduje primerjalno funkcijo na a razvrsti ()
funkcija višjega reda za naraščajoči vrstni red in druga primerjalna funkcija za razvrsti ()
za sortiranje po padajočem vrstnem redu.
Seznam 2. Funkcija višjega reda v Javi (Sort.java)
uvoz java.util.Comparator; javni razred Razvrsti {public static void main (String [] args) {String [] innerplanets = {"Merkur", "Venera", "Zemlja", "Mars"}; smetišče (notranji planeti); sort (innerplanets, new Comparator () {@Override public int compare (String e1, String e2) {return e1.compareTo (e2);}}); smetišče (notranji planeti); sort (innerplanets, new Comparator () {@Override public int compare (String e1, String e2) {return e2.compareTo (e1);}}); smetišče (notranji planeti); } statično izpraznitev praznine (matrika T []) {za (element T: matrika) System.out.println (element); System.out.println (); } static void sort (matrika T [], primerjalnik cmp) {for (int pass = 0; pass mimo; i--) if (cmp.compare (array [i], array [pass]) <0) swap (array, i, pass); } zamenjava statične praznine (matrika T [], int i, int j) {T temp = matrika [i]; polje [i] = polje [j]; polje [j] = temp; }}
Seznam 2 uvozi java.util.Comparator
funkcionalni vmesnik, ki opisuje funkcijo, ki lahko izvede primerjavo dveh predmetov poljubnega, a enakega tipa.
Dva pomembna dela te kode sta razvrsti ()
(ki implementira algoritem Bubble Sort) in razvrsti ()
klici v glavni ()
metoda. Čeprav razvrsti ()
še zdaleč ni funkcionalen, prikazuje funkcijo višjega reda, ki kot argument prejme funkcijo - primerjalnik. To funkcijo izvede tako, da prikliče svojo primerjaj ()
metoda. Dva primerka te funkcije se preneseta v dveh razvrsti ()
pokliče glavni ()
.
Sestavite seznam 2, kot sledi:
javac Sort.java
Nastalo aplikacijo zaženite na naslednji način:
java Razvrsti
Upoštevati morate naslednje rezultate:
Merkur Venera Zemlja Mars Zemlja Mars Merkur Venera Venera Merkur Mars Zemlja
Leno vrednotenje v Javi
Leno vrednotenje je še ena funkcionalna tehnika programiranja, ki za Javo 8 ni nova. Ta tehnika odloži ocenjevanje izraza, dokler ni potrebna njegova vrednost. V večini primerov Java nestrpno oceni izraz, ki je vezan na spremenljivko. Java podpira leno vrednotenje za naslednjo določeno skladnjo:
- Logično
&&
in||
, ki ne bodo ocenili svojega desnega operanda, če je levi operand false (&&
) ali resnično (||
). - The
?:
operator, ki ovrednoti logični izraz in nato oceni le enega od dveh nadomestnih izrazov (združljivega tipa), ki temelji na vrednosti true / false logičnega izraza.
Funkcionalno programiranje spodbuja izrazno usmerjeno programiranje, zato se boste želeli čim bolj izogniti uporabi stavkov. Denimo, da želite zamenjati Javo če
-drugače
izjava z ifThenElse ()
metoda. Seznam 3 prikazuje prvi poskus.
Seznam 3. Primer nestrpne ocene v Javi (EagerEval.java)
javni razred 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, kvadrat (4), kocka (4))); } statična kocka int (int x) {System.out.println ("v kocki"); vrnitev x * x * x; } static int ifThenElse (logični predikat, int onTrue, int onFalse) {return (predikat)? onTrue: onFalse; } statični int kvadrat (int x) {System.out.println ("v kvadratu"); vrnitev x * x; }}
Seznam 3 opredeljuje ifThenElse ()
metoda, ki zajema logični predikat in par celih števil, ki vrneta onTrue
celo število, ko je predikat prav in onFalse
celo število sicer.
Seznam 3 prav tako opredeljuje kocka ()
in kvadrat ()
metode. V skladu s tem te metode kocke in kvadrat celo število in vrne rezultat.
The glavni ()
metoda prikliče ifThenElse (true, kvadrat (4), kocka (4))
, ki naj se sklicuje samo kvadrat (4)
, čemur sledi ifThenElse (napačno, kvadrat (4), kocka (4))
, ki naj se sklicuje samo kocka (4)
.
Sestavite seznam 3, kot sledi:
javac EagerEval.java
Nastalo aplikacijo zaženite na naslednji način:
java EagerEval
Upoštevati morate naslednje rezultate:
v kvadratu v kocki 16 v kvadratu v kocki 64
Rezultat kaže, da vsak ifThenElse ()
rezultat klica pri izvajanju obeh metod, ne glede na logični izraz. Ne moremo izkoristiti ?:
lenoba operaterja, ker Java vneto ocenjuje argumente metode.
Čeprav se ne moremo izogniti nestrpni oceni argumentov metode, lahko vseeno izkoristimo ?:
je leno vrednotenje, da se zagotovi le to kvadrat ()
ali kocka ()
je poklican. Seznam 4 prikazuje, kako.
Seznam 4. Primer lenega vrednotenja v Javi (LazyEval.java)
vmesniška funkcija {R velja (T t); } javni razred LazyEval {public static void main (String [] args) {Function square = new Function () {{System.out.println ("SQUARE"); } @Override public Integer apply (Integer t) {System.out.println ("v kvadratu"); vrnitev t * t; }}; Kocka funkcije = nova funkcija () {{System.out.println ("CUBE"); } @Override public Integer apply (Integer t) {System.out.println ("v kocki"); vrnitev t * t * t; }}; System.out.printf ("% d% n", ifThenElse (true, kvadrat, kocka, 4)); System.out.printf ("% d% n", ifThenElse (napačno, kvadrat, kocka, 4)); } static R ifThenElse (logični predikat, funkcija onTrue, funkcija onFalse, T t) {return (predikat? onTrue.apply (t): onFalse.apply (t)); }}
Seznam 4 zavojev ifThenElse ()
v funkcijo višjega reda z izjavo, da ta metoda prejme par Funkcija
argumenti. Čeprav so ti argumenti nestrpno ovrednoteni, ko so predani ifThenElse ()
, ?:
povzroči, da se izvaja le ena od teh funkcij (prek uporabi ()
). Pri sestavljanju in zagonu aplikacije lahko opazite nestrpno in leno vrednotenje pri delu.
Sestavite seznam 4, kot sledi:
javac LazyEval.java
Nastalo aplikacijo zaženite na naslednji način:
java LazyEval
Upoštevati morate naslednje rezultate:
KVADRATNA KOCKA v kvadratu 16 v kocki 64
Leni iterator in še več
Neal Ford "Lenoba, 1. del: raziskovanje lenobnega vrednotenja v Javi" ponuja večji vpogled v lenobno vrednotenje. Avtor predstavlja leni iterator, ki temelji na Javi, in nekaj leno usmerjenih okvirov Java.
Zapore v Javi
Anonimni primerek notranjega razreda je povezan z zaključek. Deklarirati je treba spremenljivke zunanjega obsega dokončno
ali (od Java 8) dejansko dokončno (kar pomeni, da po inicializaciji ni spremenjen), da bi bil dostopen. Razmislite o seznamu 5.
Seznam 5. Primer zapiranja v Javi (PartialAdd.java)
funkcija vmesnika {R velja (T t); } javni razred PartialAdd {Funkcija dodaj (končni int x) {Funkcija partAdd = nova funkcija () {@Override public Integer apply (Integer y) {return y + x; }}; vrni delnoAdd; } javna statična void main (String [] args) {PartialAdd pa = new PartialAdd (); Funkcija add10 = pa.add (10); Funkcija add20 = pa.add (20); System.out.println (add10.apply (5)); System.out.println (add20.apply (5)); }}
Seznam 5 je enakovreden zaprtju Java, ki sem ga prej predstavil v JavaScript (glej 1. del, seznam 8). Ta koda razglaša dodaj ()
funkcija višjega reda, ki vrne funkcijo za izvajanje delne uporabe funkcije dodaj ()
funkcijo. The uporabi ()
metoda dostopa do spremenljivke x
v zunanjem obsegu dodaj ()
, ki ga je treba prijaviti dokončno
pred Java 8. Koda se obnaša skoraj enako kot enakovreden JavaScript.
Sestavite seznam 5, kot sledi:
javac PartialAdd.java
Nastalo aplikacijo zaženite na naslednji način:
java DelnoDodaj
Upoštevati morate naslednje rezultate:
15 25
Currying v Javi
Morda ste opazili, da DelnoDodaj
na seznamu 5 prikazuje več kot le zapiranje. To tudi dokazuje currying, kar je način za prevajanje ocene funkcije z več argumenti v oceno enakovrednega zaporedja funkcij z enim argumentom. Oboje pa.doda (10)
in pa.doda (20)
v seznamu 5 vrne zaprtje, ki zapiše operand (10
ali 20
) in funkcija, ki izvaja seštevanje - drugi operand (5
) se posreduje prek add10.apply (5)
ali add20.apply (5)
.
Currying nam omogoča, da ovrednotimo argumente funkcije naenkrat, pri čemer na vsaki stopnji ustvarimo novo funkcijo z enim argumentom manj. Na primer v DelnoDodaj
aplikacijo, uvajamo naslednjo funkcijo:
f (x, y) = x + y
Lahko uporabimo oba argumenta hkrati in dobimo naslednje:
f (10, 5) = 10 + 5
Vendar pri curryju uporabimo le prvi argument, ki daje naslednje:
f (10, y) = g (y) = 10 + y
Zdaj imamo eno samo funkcijo, g
, ki zajema samo en argument. To je funkcija, ki se bo ovrednotila, ko bomo poklicali uporabi ()
metoda.
Delna uporaba, ne delni dodatek
Ime DelnoDodaj
pomeni delna uporaba od dodaj ()
funkcijo. Ne pomeni delnega dodajanja. Currying je izvedba delne uporabe funkcije. Ne gre za izvajanje delnih izračunov.
Mogoče vas bo zmotila beseda "delna uporaba", še posebej zato, ker sem v prvem delu izjavil, da currying ni isto kot delna uporaba, ki je postopek fiksiranja številnih argumentov na funkcijo, ki ustvarja drugo funkcijo manjše arity. Z delno aplikacijo lahko ustvarite funkcije z več kot enim argumentom, pri curryingu pa mora imeti vsaka funkcija točno en argument.
Seznam 5 predstavlja majhen primer kariranja na osnovi Jave pred Java 8. Zdaj razmislite o CurriedCalc
v seznamu 6.
Seznam 6. Kaririranje v kodi Java (CurriedCalc.java)
funkcija vmesnika {R velja (T t); } javni razred CurriedCalc {public static void main (String [] args) {System.out.println (calc (1) .apply (2) .apply (3) .apply (4)); } statična funkcija> calc (končno celo število a) {vrni novo funkcijo> () {@Preveri javno funkcijo uveljavi (končno celo število b) {vrni novo funkcijo() {Uporabi se javna funkcija Oververde (končno celo število c) {vrni novo funkcijo () {@Override public Integer apply (Celo število d) {return (a + b) * (c + d); }}; }}; }}; }}
Seznam 6 uporablja currying za oceno funkcije f (a, b, c, d) = (a + b) * (c + d)
. Glede na izraz calc (1) .apply (2) .apply (3) .apply (4)
, ta funkcija je narejena na naslednji način:
f (1, b, c, d) = g (b, c, d) = (1 + b) * (c + d)
g (2, c, d) = h (c, d) = (1 + 2) * (c + d)
h (3, d) = i (d) = (1 + 2) * (3 + d)
i (4) = (1 + 2) * (3 + 4)
Sestavite seznam 6:
javac CurriedCalc.java
Zaženite nastalo aplikacijo:
java CurriedCalc
Upoštevati morate naslednje rezultate:
21
Ker gre pri curryingu za delno uporabo funkcije, ni pomembno, v kakšnem vrstnem redu se uporabijo argumenti. Na primer, namesto da bi opravil a
do kalc ()
in d
do najbolj ugnezdenih uporabi ()
metodo (ki izvaja izračun), bi lahko imena teh parametrov obrnili. To bi povzročilo d c b a
namesto a b c d
, vendar bi še vedno dosegel enak rezultat 21
. (Izvorna koda za to vadnico vključuje nadomestno različico CurriedCalc
.)
Funkcionalno programiranje v Javi 8
Funkcionalno programiranje pred Java 8 ni lepo. Za ustvarjanje, posredovanje funkcije in / ali vrnitev funkcije iz prvovrstne funkcije je potrebno preveč kode. Prejšnje različice Jave nimajo tudi vnaprej določenih funkcionalnih vmesnikov in prvovrstnih funkcij, kot sta filter in zemljevid.
Java 8 zmanjšuje podrobnost z uvedbo lambdas in sklicev na metode v jeziku Java. Ponuja tudi vnaprej določene funkcionalne vmesnike, prek API-ja Streams pa omogoča dostop do prvovrstnih funkcij za filtriranje, preslikavo, zmanjšanje in druge funkcije za večkratno uporabo.
Te izboljšave si bomo skupaj ogledali v naslednjih razdelkih.
Pisanje lambdas v kodo Java
A lambda je izraz, ki opisuje funkcijo z oznako izvedbe funkcionalnega vmesnika. Tu je primer:
() -> System.out.println ("moja prva lambda")
Od leve proti desni, ()
identificira lambda formalni seznam parametrov (parametrov ni), ->
pomeni lambda izraz in System.out.println ("moja prva lambda")
je telo lambde (koda, ki jo je treba izvršiti).
Lambda ima tip, ki je kateri koli funkcijski vmesnik, za katerega je lambda izvedba. Ena takih vrst je java.lang.Teče
, Ker Teče
je void run ()
metoda ima tudi prazen seznam formalnih parametrov:
Runnable r = () -> System.out.println ("moja prva lambda");
Lahko greš mimo lambde kamor koli Teče
potreben je argument; na primer Navoj (izvedljivo r)
konstruktor. Če predpostavimo, da se je zgodila prejšnja naloga, bi lahko opravili r
temu konstruktorju, kot sledi:
nova nit (r);
Lahko pa lambdo posredujete neposredno konstruktorju:
nova nit (() -> System.out.println ("moja prva lambda"));
To je vsekakor bolj kompaktno kot različica pred Java 8:
new Thread (new Runnable () {@Override public void run () {System.out.println ("moja prva lambda");}});
Datotečni filter na osnovi lambda
Moja prejšnja predstavitev funkcij višjega reda je predstavljala datotečni filter, ki temelji na anonimnem notranjem razredu. Tu je ekvivalent na osnovi lambde:
Datoteka [] txtFiles = nova datoteka ("."). ListFiles (p -> p.getAbsolutePath (). ENDWith ("txt"));
Vrni izjave v lambda izrazih
V prvem delu sem omenil, da funkcionalni programski jeziki delujejo z izrazi v nasprotju z izjavami. Pred Java 8 ste lahko v funkcionalnem programiranju v veliki meri odpravili stavke, vendar ne vrnitev
izjavo.
Zgornji fragment kode kaže, da lambda ne zahteva vrnitev
stavek za vrnitev vrednosti (v tem primeru logična vrednost true / false): samo podate izraz brez vrnitev
[in dodajte] podpičje. Vendar pa boste za lambdas z več izjavami še vedno potrebovali vrnitev
izjavo. V teh primerih morate telo lambde postaviti med oklepaje, kot sledi (ne pozabite podpičja za zaključek stavka):
Datoteka [] txtFiles = nova datoteka ("."). ListFiles (p -> {vrni p.getAbsolutePath (). EndWith ("txt");});
Lambde s funkcionalnimi vmesniki
Še dva primera ponazarjam jedrnatost lambd. Najprej ponovno poglejmo glavni ()
metoda iz Razvrsti
aplikacija, prikazana v seznamu 2:
javna statična praznina main (String [] args) {String [] innerplanets = {"Merkur", "Venera", "Zemlja", "Mars"}; smetišče (notranji planeti); razvrsti (innerplanets, (e1, e2) -> e1.compareTo (e2)); smetišče (notranji planeti); razvrsti (innerplanets, (e1, e2) -> e2.compareTo (e1)); smetišče (notranji planeti); }
Lahko tudi posodobimo kalc ()
metoda iz CurriedCalc
aplikacija, prikazana v seznamu 6:
statična funkcija> calc (Celo število a) {vrni b -> c -> d -> (a + b) * (c + d); }
Teče
, FileFilter
, in Primerjalnik
so primeri funkcionalni vmesniki, ki opisujejo funkcije. Java 8 je ta koncept formalizirala tako, da je zahtevala funkcionalni vmesnik, označen z java.lang.FunctionalInterface
vrsta pripisa, kot v @FunctionalInterface
. Vmesnik, ki je označen s to vrsto, mora prijaviti natanko eno abstraktno metodo.
Uporabite lahko vnaprej določene funkcionalne vmesnike Java (o katerih bomo razpravljali kasneje) ali pa jih preprosto določite na naslednji način:
Funkcija vmesnika @FunctionalInterface {R velja (T t); }
Nato lahko uporabite ta funkcionalni vmesnik, kot je prikazano tukaj:
javni statični void main (String [] args) {System.out.println (getValue (t -> (int) (Math.random () * t), 10)); System.out.println (getValue (x -> x * x, 20)); } statično celo število getValue (funkcija f, int x) {return f.apply (x); }
Ste novi pri lambdah?
Če ste lambdas šele nov, boste morda potrebovali več izkušenj, da boste razumeli te primere. V tem primeru si oglejte moj nadaljnji uvod v lambde in funkcionalne vmesnike v "Začnite z lambda izrazi v Javi." Na to temo boste našli tudi številne koristne objave v spletnem dnevniku. En primer je "Funkcionalno programiranje s funkcijami Java 8", v katerem avtor Edwin Dalorzo pokaže, kako uporabljati lambda izraze in anonimne funkcije v Javi 8.
Arhitektura lambde
Vsaka lambda je navsezadnje primer nekega razreda, ki nastane v zakulisju. Raziščite naslednje vire, če želite izvedeti več o lambda arhitekturi:
- "Kako delujejo lambde in anonimni notranji razredi" (Martin Farrell, DZone)
- "Lambde na Javi: pokuka pod kapo" (Brian Goetz, GOTO)
- "Zakaj se lambde Java 8 prikličejo z uporabo invokedynamic?" (Preobremenitev)
Mislim, da vam bo še posebej fascinantna video predstavitev arhitekta Java Briana Goetza o dogajanju pod pokrovom z lambdami.
Sklici na metode v Javi
Nekatere lambde se sklicujejo samo na obstoječo metodo. Na primer, prikliče se naslednja lambda System.out
je nične natisnjene številke
metoda na samem argumenta lambda:
(String s) -> System.out.println (s)
Lambda predstavlja (Nizov)
kot njegov formalni seznam parametrov in telo kode, katere System.out.println (s)
izrazi s
vrednost v standardni izhodni tok.
Če želite shraniti pritiske tipk, lahko lambdo zamenjate z sklic na metodo, kar je kompaktno sklicevanje na obstoječo metodo. Prejšnji fragment kode lahko na primer nadomestite z naslednjim:
System.out :: println
Tukaj, ::
pomeni, da System.out
je void println (nizi)
referenčna metoda. Rezultat sklica na metodo je veliko krajši, kot smo ga dosegli s prejšnjo lambdo.
Referenca metode za Razvrsti
Pred tem sem pokazal lambda različico Razvrsti
aplikacija iz seznama 2. Tu je ista koda, napisana namesto reference:
javna statična praznina main (String [] args) {String [] innerplanets = {"Merkur", "Venera", "Zemlja", "Mars"}; smetišče (notranji planeti); razvrsti (innerplanets, String :: compareTo); smetišče (notranji planeti); sort (innerplanets, Comparator.comparing (String :: toString) .reversed ()); smetišče (notranji planeti); }
The String :: compareTo
različica metode je krajša od lambda različice (e1, e2) -> e1.compareTo (e2)
. Upoštevajte pa, da je za ustvarjanje enakovredne vrstne vrstice v obratnem vrstnem redu, ki vključuje tudi sklic na metodo, potreben daljši izraz: String :: toString
. Namesto da bi navedli String :: toString
, Lahko bi določil ekvivalent s -> s.toString ()
lambda.
Več o referencah metod
Napotkov na metode je veliko več, kot bi jih lahko pokrival v omejenem prostoru. Če želite izvedeti več, si oglejte moj uvod v pisanje referenc na metode za statične metode, nestatične metode in konstruktorje v "Začnite s sklici na metode v Javi."
Vnaprej določeni funkcionalni vmesniki
Java 8 je predstavila vnaprej določene funkcionalne vmesnike (java.util.function
), tako da razvijalci ne bi morali ustvarjati lastnih funkcionalnih vmesnikov za običajne naloge. Tu je nekaj primerov:
- The
Potrošnik
funkcijski vmesnik predstavlja operacijo, ki sprejme en sam vhodni argument in ne vrne nobenega rezultata. Svojeneveljavno sprejme (T t)
metoda izvede to operacijo na argumentut
. - The
Funkcija
funkcijski vmesnik predstavlja funkcijo, ki sprejme en argument in vrne rezultat. SvojeR velja (T t)
metoda uporablja to funkcijo za argumentt
in vrne rezultat. - The
Predikat
funkcijski vmesnik predstavlja predikat (Logična funkcija) enega argumenta. Svojelogični test (T t)
metoda ovrednoti ta predikat na argumentt
in vrne true ali false. - The
Dobavitelj
funkcionalni vmesnik predstavlja dobavitelja rezultatov. SvojeDobim ()
metoda ne prejme nobenega argumenta, ampak vrne rezultat.
The DaysInMonth
aplikacija iz seznama 1 je pokazala popolno Funkcija
vmesnik. Začenši z Javo 8 lahko ta vmesnik odstranite in uvozite enako vnaprej določeno Funkcija
vmesnik.
Več o vnaprej določenih funkcionalnih vmesnikih
"Začnite z lambda izrazi v Javi" vsebuje primere Potrošnik
in Predikat
funkcionalni vmesniki. Oglejte si objavo v spletnem dnevniku "Java 8 - leno vrednotenje argumentov" in poiščite zanimivo uporabo za Dobavitelj
.
Čeprav so vnaprej določeni funkcionalni vmesniki koristni, pa predstavljajo tudi nekaj težav. Bloger Pierre-Yves Saumont pojasnjuje, zakaj.
Funkcionalni API-ji: tokovi
Java 8 je predstavil Streams API za olajšanje zaporedne in vzporedne obdelave podatkovnih postavk. Ta API temelji na potoki, kje tok je zaporedje elementov, ki izvirajo iz vira in podpirajo zaporedne in vzporedne agregatne operacije. A vir shrani elemente (na primer zbirko) ali ustvari elemente (na primer generator naključnih števil). An agregat je rezultat, izračunan iz več vhodnih vrednosti.
Tok podpira vmesne in terminalne operacije. An vmesno delovanje vrne nov tok, medtem ko a delovanje terminala porabi tok. Operacije so povezane v a cevovod (prek veriženja metod). Cevovod se začne z virom, čemur sledi nič ali več vmesnih operacij in konča s terminalom.
Tok je primer a funkcionalni API. Ponuja filtriranje, preslikavo, zmanjševanje in druge prvovrstne funkcije za večkratno uporabo. Ta API sem na kratko predstavil v Zaposleni
aplikacija, prikazana v delu 1, seznam 1. Seznam 7 ponuja še en primer.
Seznam 7. Funkcionalno programiranje s prenosi (StreamFP.java)
uvoz java.util.Random; uvoz java.util.stream.IntStream; javni razred 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 (); String [] mest = {"New York", "London", "Paris", "Berlin", "BrasÌlia", "Tokyo", "Peking", "Jerusalem", "Cairo", "Riyadh", "Moscow" }; 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 glavni ()
metoda najprej ustvari tok psevdonaključnih celih števil, ki se začnejo na 0 in končajo na 10. Tok je omejen na natanko 10 celih števil. The filter ()
prvovrstna funkcija prejme lambdo kot svoj predikatni argument. Predikat iz toka odstrani neparna cela števila. Končno, za vsakogar()
prvovrstna funkcija natisne vsako celo celo število na standardni izhod prek System.out :: println
sklic na metodo.
The glavni ()
metoda nato ustvari celoštevilski tok, ki ustvari zaporedni obseg celih števil, ki se začne pri 0 in konča pri 10. The mapToObj ()
prvovrstna funkcija prejme lambda, ki preslika celo število v enakovreden niz pri celoštevilskem indeksu v mesta
matriko. Nato se ime mesta pošlje na standardni izhod prek za vsakogar()
prvovrstna funkcija in njena System.out :: println
sklic na metodo.
Nazadnje, glavni ()
dokazuje zmanjšanje ()
prvovrstna funkcija. Celoštevilni tok, ki ustvari enak obseg celih števil kot v prejšnjem primeru, se zmanjša na vsoto njihovih vrednosti, ki se nato izpiše.
Prepoznavanje vmesnih in terminalnih operacij
Vsak od meja ()
, filter ()
, obseg ()
, in mapToObj ()
so vmesne operacije, medtem ko za vsakogar()
in zmanjšanje ()
so terminali.
Sestavite seznam 7, kot sledi:
javac StreamFP.java
Nastalo aplikacijo zaženite na naslednji način:
java StreamFP
Opazil sem naslednji izhod iz enega poteka:
0 2 10 6 0 8 10 New York London Pariz Berlin BrasÌlia Tokio Peking Jeruzalem Kairo Rijad Moskva 45 45
Morda ste pričakovali 10 namesto 7 psevdonaključnih celo celih števil (od 0 do 10, zahvaljujoč obseg (0, 11)
), ki se prikaže na začetku izhoda. Konec koncev, meja (10)
kaže, da bo izpisanih 10 celih števil. Vendar temu ni tako. Čeprav je meja (10)
rezultat klica v toku natanko 10 celih števil, filter (x -> x% 2 == 0)
rezultat klica je, da se iz toka odstranijo neparna cela števila.
Več o potokih
Če ne poznate tokov, za več informacij o tem funkcionalnem API-ju preberite mojo vadnico o uvajanju novega API-ja Streams Java SE 8.
V zaključku
Številni razvijalci Java se ne bodo lotili zgolj funkcionalnega programiranja v jeziku, kot je Haskell, ker se tako močno razlikuje od znane nujne, objektno usmerjene paradigme. Funkcije funkcionalnega programiranja Java 8 so namenjene premostitvi te vrzeli, tako da razvijalcem Java omogočajo pisanje kode, ki je lažje razumljiva, vzdrževana in preizkušena. Funkcionalna koda je tudi bolj uporabna in primernejša za vzporedno obdelavo v Javi. Z vsemi temi spodbudami res ni razloga, da v svojo kodo Java ne vključite funkcionalnih programskih možnosti Java.
Napišite funkcionalno aplikacijo za razvrščanje po mehurčkih
Funkcionalno razmišljanje je izraz, ki ga je skoval Neal Ford in se nanaša na kognitivni premik od objektno usmerjene paradigme k paradigmi funkcionalnega programiranja. Kot ste videli v tej vadnici, se lahko veliko naučite o funkcionalnem programiranju s prepisovanjem objektno usmerjene kode z uporabo funkcionalnih tehnik.
Omejite vse, kar ste se do zdaj naučili, tako da ponovno obiščete aplikacijo Razvrsti iz seznama 2. V tem kratkem nasvetu vam bom pokazal, kako napiši čisto funkcionalno razvrščanje mehurčkov, najprej z uporabo tehnik pred Java 8, nato pa s funkcionalnimi lastnostmi Java 8.
To zgodbo, "Funkcionalno programiranje za razvijalce Java, 2. del", je prvotno objavil JavaWorld.