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:
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)
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 Ajettava
on 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
, FileFilter
ja 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.out
on 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 s
arvon 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.out
on 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. Senmitätön hyväksy (T t)
method suorittaa tämän operaation argumentillat
. -
Toiminto
toiminnallinen rajapinta edustaa toimintoa, joka hyväksyy yhden argumentin ja palauttaa tuloksen. SenR sovelletaan (T t)
method soveltaa tätä funktiota argumenttiint
ja palauttaa tuloksen. -
Predikaatti
toiminnallinen rajapinta edustaa a predikaatti (Boolen-arvoinen funktio) yhdestä argumentista. Senlooginen testi (T t)
menetelmä arvioi tämän predikaatin argumentillat
ja palauttaa tosi tai epätosi. -
Toimittaja
toiminnallinen rajapinta edustaa tulosten toimittajaa. SenT 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.