Programiranje

Funkcionalno programiranje za razvijalce Java, 2. del

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:

  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)

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čeje 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.outje 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 svrednost 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.outje 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. Svoje neveljavno sprejme (T t) metoda izvede to operacijo na argumentu t.
  • The Funkcija funkcijski vmesnik predstavlja funkcijo, ki sprejme en argument in vrne rezultat. Svoje R velja (T t) metoda uporablja to funkcijo za argument t in vrne rezultat.
  • The Predikat funkcijski vmesnik predstavlja predikat (Logična funkcija) enega argumenta. Svoje logični test (T t) metoda ovrednoti ta predikat na argument t in vrne true ali false.
  • The Dobavitelj funkcionalni vmesnik predstavlja dobavitelja rezultatov. Svoje Dobim () 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.

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