Ohjelmointi

Toimiva ohjelmointi Java-kehittäjille, osa 2

Tervetuloa takaisin tähän kaksiosaiseen opetusohjelmaan, jossa esitellään toiminnallinen ohjelmointi Java-kontekstissa. Java-kehittäjien toiminnallisen ohjelmoinnin osassa 1 käytin JavaScript-esimerkkien avulla aloittaaksesi viisi toiminnallista ohjelmointitekniikkaa: puhtaat toiminnot, korkeamman tason toiminnot, laiska arviointi, sulkemiset ja curry. Näiden esimerkkien esittäminen JavaScript-muodossa antoi meille mahdollisuuden keskittyä tekniikoihin yksinkertaisemmassa syntaksissa joutumatta Javan monimutkaisempiin toiminnallisiin ohjelmointimahdollisuuksiin.

Osassa 2 tarkastelemme näitä tekniikoita käyttäen Java-koodia, joka on vanhempi kuin Java 8. Kuten näette, tämä koodi on toimiva, mutta sitä ei ole helppo kirjoittaa tai lukea. Sinulle esitellään myös uudet toiminnalliset ohjelmointiominaisuudet, jotka integroitiin täysin Java 8: n Java-kieleen; nimittäin lambdas, menetelmäviitteet, toiminnalliset rajapinnat ja Streams API.

Tässä opetusohjelmassa käymme läpi esimerkkejä osasta 1 nähdäksesi kuinka JavaScript- ja Java-esimerkit vertailevat. Näet myös, mitä tapahtuu, kun päivitän joitain Java 8 -käyttöä edeltävistä esimerkeistä toiminnallisilla kieliominaisuuksilla, kuten lambdas ja menetelmäviitteet. Lopuksi, tämä opetusohjelma sisältää käytännön harjoituksen, joka on suunniteltu auttamaan sinua harjoitella toiminnallista ajattelua, jonka teet muuttamalla pala olio-Java-koodia sen toiminnalliseksi vastaavaksi.

lataa Hanki koodi Lataa lähdekoodi esimerkiksi sovelluksiin tässä opetusohjelmassa. Luonut Jeff Friesen JavaWorldille.

Toimiva ohjelmointi Java: lla

Monet kehittäjät eivät ymmärrä sitä, mutta toiminnalliset ohjelmat oli mahdollista kirjoittaa Java-käyttöjärjestelmään ennen Java 8 -käyttöjärjestelmää. Tarkastelemme nopeasti toiminnallisia ohjelmointitoimintoja, jotka edeltävät Java 8 -käyttöjärjestelmää. Ne ovat laskeneet, sinulla on todennäköisesti enemmän arvoa siitä, kuinka Java 8: n uudet ominaisuudet (kuten lambdas ja toiminnalliset käyttöliittymät) ovat yksinkertaistaneet Java: n lähestymistapaa toiminnalliseen ohjelmointiin.

Java-tuen rajat toiminnalliselle ohjelmoinnille

Jopa Java 8: n toiminnallisia ohjelmointiparannuksia tehtäessä Java on välttämätön, olio-ohjelmointikieli. Sieltä puuttuu alueet ja muut ominaisuudet, jotka tekisivät siitä toimivamman. Javaa kiihdyttää myös nimellinen kirjoittaminen, joka edellyttää, että jokaisella tyypillä on oltava nimi. Näistä rajoituksista huolimatta Java: n toiminnalliset ominaisuudet omaksuvat kehittäjät hyötyvät silti siitä, että he voivat kirjoittaa suppeampaa, uudelleenkäytettävämpää ja luettavampaa koodia.

Toiminnallinen ohjelmointi ennen Java 8: ta

Anonyymit sisäiset luokat sekä rajapinnat ja sulkut ovat kolme vanhempaa ominaisuutta, jotka tukevat toiminnallista ohjelmointia Java: n vanhemmissa versioissa:

  • Nimetön sisäiset luokat antaa sinun siirtää toiminnot (joita rajapinnat kuvaavat) menetelmiin.
  • Toiminnalliset rajapinnat ovat rajapintoja, jotka kuvaavat toimintoa.
  • Sulkimet antaa sinun käyttää muuttujia niiden ulommassa laajuudessa.

Seuraavissa osioissa tarkastellaan uudelleen osassa 1 esitettyjä viittä tekniikkaa, mutta käyttämällä Java-syntaksia. Näet, kuinka kukin näistä toiminnallisista tekniikoista oli mahdollista ennen Java 8: ta.

Puhtaiden toimintojen kirjoittaminen Java: ssa

Listaus 1 näyttää lähdekoodin esimerkkisovellukselle, DaysInMonth, joka on kirjoitettu nimettömän sisäisen luokan ja toiminnallisen käyttöliittymän avulla. Tämä sovellus osoittaa, kuinka kirjoitetaan puhdas funktio, joka saavutettiin Java-ohjelmassa kauan ennen Java 8: ta.

Listaus 1. Puhdas funktio Javassa (DaysInMonth.java)

rajapinta Toiminto {R sovelletaan (T t); } public class 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} [kuukausi]; }}; System.out.printf ("huhtikuu:% d% n", himmennä (3)); System.out.printf ("elokuu:% d% n", himmennä (7)); }}

Yleinen Toiminto Rajapinta luettelossa 1 kuvaa toimintoa, jolla on yksi tyyppinen parametri T ja palautustyyppi R. Toiminto käyttöliittymä ilmoittaa R sovelletaan (T t) menetelmä, joka käyttää tätä funktiota annettuun argumenttiin.

main () menetelmä ilmentää tuntemattoman sisäisen luokan, joka toteuttaa Toiminto käyttöliittymä. Käytä() method unboxes kuukausi ja käyttää sitä indeksoimaan joukko päiviä kuukaudessa-kokonaislukuja. Tämän indeksin kokonaisluku palautetaan. (Jätän huomiotta karkausvuodet yksinkertaisuuden vuoksi.)

main () Seuraava suorittaa tämän toiminnon kahdesti kutsumalla Käytä() palata päivä lasketaan huhti- ja elokuussa. Nämä laskelmat tulostetaan myöhemmin.

Olemme onnistuneet luomaan toiminnon ja puhtaan toiminnon siinä! Muista, että a puhdas toiminto riippuu vain sen argumenteista eikä ulkoisesta tilasta. Ei ole sivuvaikutuksia.

Koosta Listaus 1 seuraavasti:

javac DaysInMonth.java

Suorita tuloksena oleva sovellus seuraavasti:

java DaysInMonth

Ota huomioon seuraava tulos:

Huhtikuu: 30. elokuuta: 31

Korkeamman tason funktioiden kirjoittaminen Java-sovelluksessa

Seuraavaksi tarkastelemme korkeamman asteen toimintoja, jotka tunnetaan myös ensimmäisen luokan funktioina. Muista, että a korkeamman asteen toiminto vastaanottaa funktion argumentteja ja / tai palauttaa funktion tuloksen. Java yhdistää funktion menetelmään, joka määritetään nimettömässä sisäisessä luokassa. Tämän luokan ilmentymä välitetään tai palautetaan toiselle Java-menetelmälle, joka toimii korkeamman asteen funktiona. Seuraava tiedostopohjainen koodifragmentti osoittaa toiminnon siirtämisen korkeamman asteen funktiolle:

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

Tämä koodifragmentti läpäisee funktion, joka perustuu java.io.FileFilter toiminnallinen käyttöliittymä java.io.File luokan File [] listFiles (FileFilter-suodatin) -menetelmää, käskemällä sitä palauttamaan vain ne tiedostot, joissa on txt laajennukset.

Listaus 2 näyttää toisen tavan työskennellä Java-tason korkeamman tason toimintojen kanssa. Tässä tapauksessa koodi välittää vertailutoiminnon a: lle järjestellä() korkeamman asteen funktio nousevassa järjestyksessä ja toinen vertailutoiminto järjestellä() laskevassa järjestyksessä.

Listaus 2. Korkeamman tason funktio Javassa (Sort.java)

tuo java.util.Comparator; julkinen luokka Lajittelu {public static void main (String [] args) {String [] sisäiset planeetat = {"Mercury", "Venus", "Earth", "Mars"}; dump (sisäiset planeetat); lajittelu (sisäiset planeetat, uusi vertailija () {@Override public int vertaile (String e1, String e2) {return e1.compareTo (e2);}}); kaatopaikka (sisempi planeetta); lajittelu (sisäiset planeetat, uusi vertailija () {@Override public int vertaile (String e1, String e2) {return e2.compareTo (e1);}}); kaatopaikka (sisempi planeetta); } static void dump (T [] array) {for (T elementti: matriisi) System.out.println (elementti); System.out.println (); } staattinen void sort (T [] -taulukko, Comparator cmp) {for (int pass = 0; pass  kulkea; i--) if (cmp.verta (taulukko [i], taulukko [läpäisy]) <0) vaihda (taulukko, i, läpäisy) } staattinen tyhjiönvaihto (T [] -ryhmä, int i, int j) {T-temp = taulukko [i]; taulukko [i] = taulukko [j]; taulukko [j] = lämpötila; }}

Listaus 2 tuo java.util.Vertailija toiminnallinen rajapinta, joka kuvaa toimintoa, jolla voidaan vertailla kahta mielivaltaista mutta identtistä objektia.

Kaksi merkittävää osaa tästä koodista ovat järjestellä() - menetelmä (joka toteuttaa Bubble Sort - algoritmin) ja järjestellä() kutsut main () menetelmä. Siitä huolimatta järjestellä() on kaukana toiminnallisuudesta, se osoittaa korkeamman asteen funktion, joka vastaanottaa funktion - vertailijan - argumenttina. Se suorittaa tämän toiminnon käyttämällä sitä vertailla() menetelmä. Kaksi tämän toiminnon esiintymää välitetään kahtena järjestellä() kutsuu sisään main ().

Koosta Listaus 2 seuraavasti:

javac Sort.java

Suorita tuloksena oleva sovellus seuraavasti:

java Lajittele

Ota huomioon seuraava tulos:

Elohopea Venus Earth Mars Maa Mars Elohopea Venus Venus Elohopea Mars Earth

Laiska arviointi Java-kielellä

Laiska arviointi on toinen toiminnallinen ohjelmointitekniikka, joka ei ole uusi Java 8: ssa. Tämä tekniikka viivästyttää lausekkeen arviointia, kunnes sen arvoa tarvitaan. Useimmissa tapauksissa Java arvioi innokkaasti lausekkeen, joka on sidottu muuttujaan. Java tukee laiska-arviointia seuraavassa syntaksissa:

  • Totuusarvo && ja || operaattorit, jotka eivät arvioi oikeaa operandiaan, kun vasen operandi on väärä (&&) tai tosi (||).
  • ?: operaattori, joka arvioi Boolen lausekkeen ja arvioi sen jälkeen vain yhden kahdesta vaihtoehtoisesta lausekkeesta (yhteensopivan tyypin) Boolen lausekkeen tosi / väärä arvon perusteella.

Toiminnallinen ohjelmointi kannustaa ilmaisukeskeiseen ohjelmointiin, joten sinun on vältettävä lausekkeiden käyttöä niin paljon kuin mahdollista. Oletetaan esimerkiksi, että haluat korvata Java-tiedostot jos-muu lausunto ja ifThenElse () menetelmä. Listaus 3 näyttää ensimmäisen yrityksen.

Listaus 3. Esimerkki innokkaasta arvioinnista Javassa (EagerEval.java)

public class EagerEval {public static void main (String [] args) {System.out.printf ("% d% n", ifThenElse (true, neliö (4), kuutio (4)); System.out.printf ("% d% n", ifThenElse (väärä, neliö (4), kuutio (4)); } staattinen int-kuutio (int x) {System.out.println ("kuutiossa"); paluu x * x * x; } staattinen int ifThenElse (looginen predikaatti, int onTue, int onFalse) {return (predikaatti)? onTrue: onFalse; } staattinen int-neliö (int x) {System.out.println ("neliössä"); paluu x * x; }}

Listaus 3 määrittelee ifThenElse () menetelmä, joka ottaa Boolen predikaatin ja parin kokonaislukuja, palauttaen totta kokonaisluku, kun predikaatti on totta ja onFalse muuten kokonaisluku.

Listaus 3 määrittelee myös kuutio() ja neliö() menetelmiä. Vastaavasti nämä menetelmät kuutioivat ja neliöivät kokonaisluvun ja palauttavat tuloksen.

main () menetelmä kutsuu ifThenElse (tosi, neliö (4), kuutio (4)), jonka pitäisi vedota vain neliö (4), jonka jälkeen ifThenElse (väärä, neliö (4), kuutio (4)), jonka pitäisi vedota vain kuutio (4).

Koosta Listaus 3 seuraavasti:

javac EagerEval.java

Suorita tuloksena oleva sovellus seuraavasti:

java EagerEval

Ota huomioon seuraava tulos:

neliössä kuutiossa 16 neliössä kuutiossa 64

Lähtö osoittaa, että kukin ifThenElse () kutsu tuloksia molemmissa menetelmissä, riippumatta Boolen lausekkeesta. Emme voi hyödyntää ?: operaattorin laiskuus, koska Java arvioi innokkaasti menetelmän argumentit.

Vaikka ei ole mitään keinoa välttää innokkaita menetelmien arviointia, voimme silti hyödyntää ?:laiska arviointi sen varmistamiseksi vain neliö() tai kuutio() kutsutaan. Listaus 4 näyttää miten.

Listaus 4. Esimerkki laiskasta arvioinnista Javassa (LazyEval.java)

rajapinta Toiminto {R sovelletaan (T t); } public class LazyEval {public static void main (String [] args) {Funktion neliö = uusi Funktio () {{System.out.println ("NELIÖ"); } @Override public Integer Apply (Kokonaisluku t) {System.out.println ("neliössä"); paluu t * t; }}; Funktiokuutio = uusi toiminto () {{System.out.println ("KUUTI"); } @Override public Integer Apply (Kokonaisluku t) {System.out.println ("kuutiossa"); paluu t * t * t; }}; System.out.printf ("% d% n", ifThenElse (tosi, neliö, kuutio, 4)); System.out.printf ("% d% n", ifThenElse (väärä, neliö, kuutio, 4)); } staattinen R ifThenElse (looginen predikaatti, Function onTrue, Function onFalse, T t) {return (predikaatti? onTrue.apply (t): onFalse.apply (t)); }}

Luettelossa 4 kierrosta ifThenElse () korkeamman asteen funktioksi ilmoittamalla tämän menetelmän vastaanottavan parin Toiminto argumentteja. Vaikka näitä väitteitä arvioidaan innokkaasti, kun ne välitetään ifThenElse (), ?: operaattori saa vain yhden näistä toiminnoista suorittamaan (kautta Käytä()). Voit nähdä sekä innokkaita että laiskoja arviointeja työssä, kun käännät ja suoritat sovelluksen.

Koosta Listaus 4 seuraavasti:

javac LazyEval.java

Suorita tuloksena oleva sovellus seuraavasti:

java LazyEval

Ota huomioon seuraava tulos:

NELIÖKUUTI neliössä 16 kuutiossa 64

Laiska iteraattori ja paljon muuta

Neal Fordin "Laisuus, osa 1: Laiskan arvioinnin tutkiminen Javassa" tarjoaa enemmän tietoa laiskasta arvioinnista. Kirjoittaja esittelee Java-pohjaisen laiska-iteraattorin sekä pari laiska-suuntautunutta Java-kehystä.

Sulkemiset Java-kielellä

Tuntematon sisäisen luokan esiintymä liittyy a päättäminen. Ulkoisen laajuuden muuttujat on ilmoitettava lopullinen tai (alkaen Java 8: sta) tosiasiallisesti lopullinen (tarkoittaa muuttamattomia alustuksen jälkeen), jotta ne ovat käytettävissä. Harkitse listaa 5.

Listaus 5. Esimerkki Java-sovelluksen sulkemisista (PartialAdd.java)

rajapinta Toiminto {R sovelletaan (T t); } public class PartialAdd {Function add (final int x) {Function particAdd = new Function () {@Override public Integer apply (Integer y) {return y + x; }}; paluu osittainLisää; } public static void main (Merkkijono [] argumentit) {PartialAdd pa = new PartialAdd (); Funktio add10 = pa.add (10); Funktio add20 = pa.add (20); System.out.println (add10.apply (5)); System.out.println (add20.apply (5)); }}

Listaus 5 on Java-ekvivalentti aiemmin JavaScriptissä esittämäni sulkemiseen (katso osa 1, luettelo 8). Tämä koodi ilmoittaa lisätä() korkeamman asteen funktio, joka palauttaa funktion suorittamaan osittaisen sovelluksen lisätä() toiminto. Käytä() menetelmä käyttää muuttujaa x - ulommassa laajuudessa lisätä(), joka on ilmoitettava lopullinen ennen Java 8. Koodi käyttäytyy suunnilleen samalla tavalla kuin JavaScripti.

Koosta Listaus 5 seuraavasti:

javac PartialAdd.java

Suorita tuloksena oleva sovellus seuraavasti:

java PartialAdd

Ota huomioon seuraava tulos:

15 25

Currying Java

Olet ehkä huomannut, että PartialAdd luettelossa 5 osoittaa muutakin kuin vain sulkemisia. Se osoittaa myös curry, joka on tapa kääntää moniargumenttisen funktion arviointi yhden argumentin funktioiden vastaavan sarjan arvioinniksi. Molemmat lis. (10) ja lis. (20) palauttaa luettelossa 5 sulkemisen, joka tallentaa operandin (10 tai 20) ja funktio, joka suorittaa lisäyksen - toinen operandi (5) välitetään add10.apply (5) tai add20.apply (5).

Curry-toiminnon avulla voimme arvioida funktion argumentteja yksi kerrallaan, jolloin saadaan uusi funktio, jossa on yksi argumentti vähemmän kussakin vaiheessa. Esimerkiksi PartialAdd sovelluksessa, valitsemme seuraavan toiminnon:

f (x, y) = x + y

Voisimme soveltaa molempia argumentteja samanaikaisesti, jolloin saatiin seuraava:

f (10, 5) = 10 + 5

Currylla käytetään kuitenkin vain ensimmäistä argumenttia, mikä antaa tämän:

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

Meillä on nyt yksi tehtävä, g, joka vie vain yhden argumentin. Tämä on toiminto, joka arvioidaan, kun kutsumme Käytä() menetelmä.

Osittainen käyttö, ei osittainen lisäys

Nimi PartialAdd tarkoittaa osittainen hakemus n lisätä() toiminto. Se ei tarkoita osittaista lisäystä. Curry-toiminnolla tarkoitetaan toiminnon osittaista soveltamista. Kyse ei ole osittaisten laskelmien suorittamisesta.

Saatat olla hämmentynyt siitä, että käytän ilmaisua "osittainen sovellus", varsinkin kun totesin osassa 1, että curry ei ole sama kuin osittainen hakemus, joka on prosessi, jolla kiinnitetään joukko argumentteja funktioon ja tuotetaan toinen pienemmällä ariteetilla varustettu funktio. Osittaisen sovelluksen avulla voit tuottaa funktioita useammalla kuin yhdellä argumentilla, mutta curry-toiminnolla jokaisella funktiolla on oltava täsmälleen yksi argumentti.

Listaus 5 esittää pienen esimerkin Java-pohjaisesta currysta ennen Java 8: ta. Harkitse nyt CurriedCalc sovellus luettelossa 6.

Listaus 6. Currying Java-koodissa (CurriedCalc.java)

rajapinta Toiminto {R sovelletaan (T t); } public class CurriedCalc {public static void main (String [] args) {System.out.println (calc (1) .apply (2) .apply (3) .apply (4)); } staattinen toiminto> calc (viimeinen kokonaisluku a) {return new Function> () {@Override public Function sovelletaan (viimeinen kokonaisluku b) {palauta uusi funktio() {@Override public Function apply (viimeinen kokonaisluku c) {return new Function () {@Override public Integer apply (kokonaisluku d) {return (a + b) * (c + d); }}; }}; }}; }}

Listaus 6 käyttää currya toiminnon arvioimiseksi f (a, b, c, d) = (a + b) * (c + d). Annettu ilmaisu laskea (1). soveltaa (2). soveltaa (3). soveltaa (4), tämä toiminto viritetään seuraavasti:

  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)

Koosta luettelo 6:

javac CurriedCalc.java

Suorita tuloksena oleva sovellus:

java CurriedCalc

Ota huomioon seuraava tulos:

21

Koska currylla tarkoitetaan funktion osittaista soveltamista, ei ole väliä missä järjestyksessä argumentteja käytetään. Esimerkiksi ohittamisen sijaan a että laskea () ja d eniten sisäkkäisiin Käytä() menetelmällä (joka suorittaa laskutoimituksen), voimme muuttaa nämä parametrien nimet. Tämä johtaisi d c b a sijasta a b c d, mutta silti saavutettaisiin sama tulos 21. (Tämän opetusohjelman lähdekoodi sisältää vaihtoehtoisen version CurriedCalc.)

Toimiva ohjelmointi Java 8: ssa

Toimiva ohjelmointi ennen Java 8: ta ei ole hieno. Liian paljon koodia tarvitaan funktion luomiseen, välittämiseen ja / tai funktion palauttamiseen ensiluokkaisesta toiminnosta. Aiemmissa Java-versioissa ei myöskään ole ennalta määriteltyjä toiminnallisia rajapintoja ja ensiluokkaisia ​​toimintoja, kuten suodatin ja kartta.

Java 8 vähentää sanallisuutta pitkälti ottamalla käyttöön lambdas- ja menetelmäviittauksia Java-kieleen. Se tarjoaa myös ennalta määriteltyjä toiminnallisia rajapintoja, ja se tekee suodatuksen, kartoituksen, pienennyksen ja muut uudelleenkäytettävät ensiluokkaiset toiminnot saataville Streams API: n kautta.

Tarkastelemme näitä parannuksia yhdessä seuraavissa osioissa.

Lambdojen kirjoittaminen Java-koodiksi

A lambda on lauseke, joka kuvaa toimintoa merkitsemällä toiminnallisen rajapinnan toteutusta. Tässä on esimerkki:

() -> System.out.println ("ensimmäinen lambda")

Vasemmalta oikealle, () tunnistaa lambdan muodollisen parametriluettelon (parametreja ei ole), -> tarkoittaa lambda-ilmaisua ja System.out.println ("ensimmäinen lambda") on lambdan runko (suoritettava koodi).

Lambdassa on tyyppi, joka on mikä tahansa toiminnallinen rajapinta, jolle lambda on toteutus. Yksi tällainen tyyppi on java.lang.Runnable, koska Ajettavaon void run () methodilla on myös tyhjä virallinen parametriluettelo:

Suoritettava r = () -> System.out.println ("ensimmäinen lambda");

Voit ohittaa lambdan missä tahansa, että a Ajettava argumentti vaaditaan; esimerkiksi Kierre (ajettava r) rakentaja. Olettaen, että edellinen tehtävä on tapahtunut, voit läpäistä r tälle rakentajalle seuraavasti:

uusi lanka (r);

Vaihtoehtoisesti voit välittää lambda suoraan rakentajalle:

uusi ketju (() -> System.out.println ("ensimmäinen lambda"));

Tämä on ehdottomasti pienempi kuin Java 8 -versiota edeltävä versio:

uusi säie (uusi Runnable () {@Override public void run () {System.out.println ("ensimmäinen lambda");}});

Lambda-pohjainen tiedostosuodatin

Aiempi esittelyni korkeamman asteen toiminnoista esitteli anonyymiin sisäiseen luokkaan perustuvan tiedostosuodattimen. Tässä on lambda-pohjainen vastine:

Tiedosto [] txtFiles = uusi tiedosto ("."). ListFiles (p -> p.getAbsolutePath (). EndWith ("txt"));

Palautuslausekkeet lambda-lausekkeilla

Osassa 1 mainitsin, että toiminnalliset ohjelmointikielet toimivat lausekkeiden kanssa lausekkeiden sijaan. Ennen Java 8: ta voit poistaa suurelta osin lauseet toiminnallisesta ohjelmoinnista, mutta et voinut poistaa sitä palata lausunto.

Yllä oleva koodinpätkä osoittaa, että lambda ei vaadi a palata lauseke palauttaa arvon (tässä tapauksessa looginen tosi / epätosi arvo): määrität vain lausekkeen ilman palata [ja lisää] puolipiste. Tarvitset kuitenkin monilausumaisten lambdojen osalta palata lausunto. Näissä tapauksissa sinun on sijoitettava lambdan runko olkainten väliin seuraavasti (älä unohda puolipistettä lopettaaksesi lauseen):

Tiedosto [] txtFiles = uusi tiedosto ("."). ListFiles (p -> {return p.getAbsolutePath (). EndWith ("txt");});

Lambdas toiminnallisilla liitännöillä

Minulla on vielä kaksi esimerkkiä havainnollistamaan lambdojen tiiviyttä. Tarkastellaan ensin main () menetelmä Järjestellä luettelossa 2 esitetty sovellus:

julkinen staattinen tyhjä pää (String [] args) {String [] sisäinen planeetta = {"Elohopea", "Venus", "Maa", "Mars"}; kaatopaikka (sisempi planeetta); lajittelu (sisemmät planeetat, (e1, e2) -> e1.vertaTo (e2)); kaatopaikka (sisempi planeetta); lajittelu (sisemmät planeetat, (e1, e2) -> e2.vertaileTo (e1)); kaatopaikka (sisempi planeetta); }

Voimme myös päivittää laskea () menetelmä CurriedCalc luettelossa 6 esitetty sovellus:

staattinen toiminto> laskettu (kokonaisluku a) {paluu b -> c -> d -> (a + b) * (c + d); }

Ajettava, FileFilterja Vertailija ovat esimerkkejä toiminnalliset rajapinnat, jotka kuvaavat toimintoja. Java 8 muodosti tämän käsitteen vaatimalla toiminnallisen käyttöliittymän merkitsemistä java.lang.FunctionalInterface merkintätyyppi, kuten kohdassa @FunctionalInterface. Tämän tyyppisellä merkinnällä varustetun käyttöliittymän on ilmoitettava täsmälleen yksi abstrakti menetelmä.

Voit käyttää Javan ennalta määritettyjä toiminnallisia rajapintoja (joista keskustellaan myöhemmin), tai voit määrittää omat helposti seuraavasti:

@FunctionalInterface -rajapinta Toiminto {R sovelletaan (T t); }

Voit sitten käyttää tätä toiminnallista käyttöliittymää seuraavalla tavalla:

public static void main (String [] args) {System.out.println (getValue (t -> (int) (Math.random () * t), 10)); System.out.println (getValue (x -> x * x, 20)); } staattinen kokonaisluku getValue (funktio f, int x) {return f.apply (x); }

Onko lambdas uusi käyttäjä?

Jos olet uusi lambdas, saatat tarvita lisää taustaa ymmärtääksesi näitä esimerkkejä. Tällöin tutustu lambda- ja toiminnallisten käyttöliittymien esittelyni kohtaan "Aloita lambda-lausekkeiden käyttö Java-ohjelmassa". Löydät myös lukuisia hyödyllisiä blogiviestejä tästä aiheesta. Yksi esimerkki on "Toiminnallinen ohjelmointi Java 8 -toiminnoilla", jossa kirjailija Edwin Dalorzo näyttää kuinka lambda-lausekkeita ja nimettömiä toimintoja käytetään Java 8: ssa.

Lambdan arkkitehtuuri

Jokainen lambda on viime kädessä jonkin luokan kohtaus, joka syntyy kulissien takana. Tutustu seuraaviin resursseihin saadaksesi lisätietoja lambda-arkkitehtuurista:

  • "Kuinka lambdas ja nimettömät sisäiset luokat toimivat" (Martin Farrell, DZone)
  • "Lambdas Javalla: kurkistus konepellin alla" (Brian Goetz, GOTO)
  • "Miksi Java 8: n lambdas kutsutaan käyttäen invokedynamic -ohjelmaa?" (Pinon ylivuoto)

Mielestäni Java-kieliarkkitehti Brian Goetzin videoesitys siitä, mitä hupun alla tapahtuu, on erityisen kiehtova.

Menetelmäviitteet Java: ssa

Jotkut lambdat käyttävät vain olemassa olevaa menetelmää. Esimerkiksi seuraava lambda vetoaa System.outon mitätön println (s) menetelmä lambdan yksittäisessä argumentissa:

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

Lambda esittelee (Merkkijonot) virallisena parametriluettelona ja koodirunko, jonka System.out.println (t) lauseke tulostaa sarvon tavalliseen lähtövirtaan.

Voit tallentaa näppäinpainallukset korvaamalla lambda: lla a menetelmän viite, joka on tiivis viittaus olemassa olevaan menetelmään. Voit esimerkiksi korvata edellisen koodinpätkän seuraavalla:

System.out :: println

Tässä, :: tarkoittaa sitä System.outon void println (merkkijonot) menetelmään viitataan. Menetelmän viite johtaa paljon lyhyempään koodiin kuin edellisellä lambdalla.

Lajitteluperuste

Esitin aiemmin lambda-version Järjestellä sovellus luettelosta 2. Tässä on sama koodi, joka on kirjoitettu metodiviitteellä sen sijaan:

julkinen staattinen tyhjä pää (String [] args) {String [] sisäinen planeetta = {"Elohopea", "Venus", "Maa", "Mars"}; kaatopaikka (sisempi planeetta); lajittelu (sisemmät planeetat, merkkijono :: vertaileTo); kaatopaikka (sisempi planeetta); lajittelu (sisäiset planeetat, Comparator.comparing (String :: toString). Reversed ()); kaatopaikka (sisempi planeetta); }

Merkkijono :: vertaa menetelmän viiteversio on lyhyempi kuin (e1, e2) -> e1.vertaile (e2). Huomaa kuitenkin, että vastaavan käänteisen järjestyksen luomiseksi tarvitaan pidempi lauseke, joka sisältää myös menetelmäviitteen: Merkkijono :: toString. Määrittelyn sijasta Merkkijono :: toString, Olisin voinut määrittää vastaavan s -> s.toString () lambda.

Lisätietoja menetelmäviitteistä

Menetelmäviitteisiin sisältyy paljon enemmän kuin voisin käsitellä rajoitetussa tilassa. Jos haluat lisätietoja, tutustu johdantoosi staattisten menetelmien, ei-staattisten menetelmien ja konstruktoreiden menetelmäviitteiden kirjoittamisesta kohdassa "Aloita Javan metodiviittausten aloittaminen".

Ennalta määritetyt toiminnalliset rajapinnat

Java 8 esitteli ennalta määritetyt toiminnalliset rajapinnat (java.util.toiminto), jotta kehittäjillä ei ole omia toiminnallisia rajapintoja yleisiin tehtäviin. Tässä on muutama esimerkki:

  • Kuluttaja toiminnallinen rajapinta edustaa operaatiota, joka hyväksyy yhden syöteargumentin ja ei palauta tulosta. Sen mitätön hyväksy (T t) method suorittaa tämän operaation argumentilla t.
  • Toiminto toiminnallinen rajapinta edustaa toimintoa, joka hyväksyy yhden argumentin ja palauttaa tuloksen. Sen R sovelletaan (T t) method soveltaa tätä funktiota argumenttiin t ja palauttaa tuloksen.
  • Predikaatti toiminnallinen rajapinta edustaa a predikaatti (Boolen-arvoinen funktio) yhdestä argumentista. Sen looginen testi (T t) menetelmä arvioi tämän predikaatin argumentilla t ja palauttaa tosi tai epätosi.
  • Toimittaja toiminnallinen rajapinta edustaa tulosten toimittajaa. Sen T get () method ei vastaanota argumentteja, mutta palauttaa tuloksen.

DaysInMonth Hakemus luettelossa 1 paljasti täydellisen Toiminto käyttöliittymä. Java 8: sta alkaen voit poistaa tämän käyttöliittymän ja tuoda saman ennalta määritetyn Toiminto käyttöliittymä.

Lisätietoja ennalta määritetyistä toiminnallisista rajapinnoista

"Aloita lambda-lausekkeiden käyttö Java-ohjelmassa" tarjoaa esimerkkejä Kuluttaja ja Predikaatti toiminnalliset rajapinnat. Tutustu blogiviestiin "Java 8 - Lazy argument assessment" löytääksesi mielenkiintoisen käyttötarkoituksen Toimittaja.

Vaikka ennalta määritetyt toiminnalliset rajapinnat ovat hyödyllisiä, ne myös esittävät joitain asioita. Bloggaaja Pierre-Yves Saumont selittää miksi.

Toiminnalliset sovellusliittymät: virrat

Java 8 esitteli Streams-sovellusliittymän helpottaakseen tietueiden peräkkäistä ja rinnakkaista käsittelyä. Tämä sovellusliittymä perustuu virrat, missä virta on alkuaineiden sarja, joka on peräisin lähteestä ja tukee peräkkäisiä ja rinnakkaisia ​​aggregaatteja. A lähde tallentaa elementtejä (kuten kokoelma) tai generoi elementtejä (kuten satunnaislukugeneraattori). An aggregaatti on tulos, joka on laskettu useista tuloarvoista.

Virta tukee väli- ja päätelaitteita. An välitoiminto palauttaa uuden virran, kun taas a päätelaitteen käyttö kuluttaa virran. Toiminnot yhdistetään a putki (menetelmäketjun kautta). Putki alkaa lähteestä, jota seuraa nolla tai useampia välitoimintoja, ja päättyy päätelaitteeseen.

Virrat on esimerkki a toiminnallinen API. Se tarjoaa suodattimen, kartan, pienennyksen ja muita uudelleenkäytettäviä ensiluokkaisia ​​toimintoja. Esitin tämän API: n lyhyesti Työntekijät Osa 1, Listaus 1 esitetty sovellus. Listaus 7 tarjoaa toisen esimerkin.

Listaus 7. Toiminnallinen ohjelmointi Streamin kanssa (StreamFP.java)

tuo java.util.Random; tuo 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 (); Jono [] kaupungit = {"New York", "Lontoo", "Pariisi", "Berliini", "BrasÌlia", "Tokio", "Peking", "Jerusalem", "Kairo", "Riad", "Moskova" }; IntStream.range (0, 11) .mapToObj (i -> kaupungit [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 :: summa)); }}

main () method luo ensin näennäissatunnaisten kokonaislukujen virran, joka alkaa nollasta ja päättyy arvoon 10. Virta on rajoitettu täsmälleen 10 kokonaislukuun. suodattaa() ensiluokkainen funktio saa lambda sen predikaatti argumenttina. Predikaatti poistaa parittomat kokonaisluvut virrasta. Lopuksi jokaiselle() ensiluokkainen toiminto tulostaa jokaisen parillisen kokonaisluvun vakiolähtöön System.out :: println menetelmän viite.

main () method luo seuraavaksi kokonaislukuvirran, joka tuottaa peräkkäisen kokonaislukualueen, joka alkaa 0: sta ja päättyy 10: een mapToObj () ensiluokkainen funktio vastaanottaa lambdan, joka kartoittaa kokonaisluvun vastaavaan merkkijonoon kaupungeissa taulukko. Sen jälkeen kaupungin nimi lähetetään vakiolähtöön jokaiselle() ensiluokkainen toiminto ja sen System.out :: println menetelmän viite.

Lopuksi, main () osoittaa vähentää() ensiluokkainen toiminto. Kokonaisluvuvirta, joka tuottaa saman kokonaislukualueen kuin edellisessä esimerkissä, pienennetään niiden arvojen summaksi, joka myöhemmin tuotetaan.

Väli- ja päätelaitteiden tunnistaminen

Jokainen raja(), suodattaa(), alue ()ja mapToObj () ovat välitoimia, kun taas jokaiselle() ja vähentää() ovat terminaalitoimintoja.

Koosta Listaus 7 seuraavasti:

javac StreamFP.java

Suorita tuloksena oleva sovellus seuraavasti:

java StreamFP

Havaitsin seuraavan tuotoksen yhdestä ajosta:

0 2 10 6 0 8 10 New York Lontoo Pariisi Berliini BrasÌlia Tokio Peking Jerusalem Kairo Riyadh Moskova 45 45

Olet ehkä odottanut 10: tä seitsemän näennäissatunnaisen parillisen sijaan (vaihtelevat 0: sta 10: een, kiitos alue (0, 11)) ilmestymään tuloksen alkuun. Kuitenkin, raja (10) näyttää osoittavan, että 10 kokonaislukua tulostetaan. Näin ei kuitenkaan ole. vaikkakin raja (10) puhelun tuloksena on täsmälleen 10 kokonaislukua, suodatin (x -> x% 2 == 0) kutsu johtaa parittomien kokonaislukujen poistamiseen virrasta.

Lisätietoja Streamista

Jos Stream ei ole sinulle tuttu, tutustu opetusohjelmaani, joka esittelee Java SE 8: n uuden Streams API: n tästä toiminnallisesta API: sta.

Tiivistettynä

Monet Java-kehittäjät eivät harjoita puhdasta toiminnallista ohjelmointia Haskellin kaltaisella kielellä, koska se eroaa niin suuresti tutusta välttämättömästä, olio-orientoidusta paradigmasta. Java 8: n toiminnalliset ohjelmointimahdollisuudet on suunniteltu tämän aukon poistamiseksi, mikä antaa Java-kehittäjille mahdollisuuden kirjoittaa helpommin ymmärrettävää, ylläpidettävää ja testattavaa koodia. Toiminnallinen koodi on myös uudelleenkäytettävissä ja soveltuu paremmin rinnakkaiskäsittelyyn Java-tilassa. Kaikilla näillä kannustimilla ei todellakaan ole mitään syytä olla sisällyttämättä Javan toiminnallisia ohjelmointivaihtoehtoja Java-koodiin.

Kirjoita toimiva Bubble Sort -sovellus

Toiminnallinen ajattelu on Neal Fordin keksimä termi, joka viittaa kognitiiviseen siirtymiseen olio-paradigmasta funktionaaliseen ohjelmointiparadigmaan. Kuten olet nähnyt tässä opetusohjelmassa, on mahdollista oppia paljon toiminnallisesta ohjelmoinnista kirjoittamalla uudelleen objektisuuntainen koodi toiminnallisten tekniikoiden avulla.

Päätä tähän mennessä oppimasi tilaamalla Lajittelu-sovellus luettelosta 2. Tässä pikavinkissä näytän sinulle, kuinka kirjoita puhtaasti toimiva Bubble Sort, ensin käyttämällä Java 8 -käyttöä edeltäviä tekniikoita ja sitten Java 8: n toiminnallisia ominaisuuksia.

Tämän tarinan "Funktionaalinen ohjelmointi Java-kehittäjille, osa 2" julkaisi alun perin JavaWorld.

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