Programiranje

Programiranje Java z lambda izrazi

V tehničnem uvodnem nagovoru za JavaOne 2013 je Mark Reinhold, glavni arhitekt skupine Java Platform Group pri podjetju Oracle, lambda izraze opisal kot največjo nadgradnjo programskega modela Java. kdajkoli. Čeprav obstaja veliko aplikacij za lambda izraze, se ta članek osredotoča na poseben primer, ki se pogosto pojavlja v matematičnih aplikacijah; in sicer potreba po posredovanju funkcije algoritmu.

Kot sivolaki geek sem v preteklih letih programiral v številnih jezikih in že od različice 1.1 intenzivno programiram na Javi. Ko sem začel delati z računalniki, skoraj nihče ni diplomiral iz računalništva. Računalniški strokovnjaki so prihajali večinoma iz drugih disciplin, kot so elektrotehnika, fizika, poslovanje in matematika. V svojem nekdanjem življenju sem bil matematik in zato ne bi smelo presenetiti, da je bil moj prvotni pogled na računalnik pogled na velikanski programirljiv kalkulator. Z leti sem precej razširil svoj pogled na računalnike, vendar vseeno pozdravljam možnost dela na aplikacijah, ki vključujejo nekatere vidike matematike.

Številne matematične aplikacije zahtevajo, da se funkcija posreduje kot parameter algoritmu. Primeri iz univerzitetne algebre in osnovnega računa vključujejo reševanje enačbe ali računanje integrala funkcije. Že več kot 15 let je bila Java izbrani programski jezik za večino aplikacij, vendar je bil prvi jezik, ki sem ga pogosto uporabljal, in mi ni dovolil, da funkcijo (tehnično kazalec ali sklic na funkcijo) prenesem kot na enostaven in enostaven način. Ta pomanjkljivost se bo kmalu spremenila s prihajajočo izdajo Jave 8.

Moč lambda izrazov seže precej dlje od primera enkratne uporabe, vendar bi vam preučevanje različnih izvedb istega primera moralo dati trden občutek, kako bodo lambde koristile vašim programom Java. V tem članku bom z običajnim primerom opisal težavo, nato ponudil rešitve, napisane v jeziku C ++, Java pred lambda izrazi in Java z lambda izrazi. Upoštevajte, da za razumevanje in razumevanje glavnih točk tega članka ni potrebno močno znanje matematike.

Spoznavanje lambdas

Lambda izrazi, znani tudi kot zapiranja, funkcijski dobesedni izrazi ali preprosto lambda, opisujejo nabor funkcij, opredeljenih v zahtevi za specifikacijo Java (JSR) 335. Manj formalni / bolj berljivi uvodi za lambda izraze so na voljo v razdelku najnovejše različice Vadnica za Java in v nekaj člankih Briana Goetza, "Stanje lambde" in "Stanje lambde: izdaja knjižnic." Ti viri opisujejo sintakso lambda izrazov in ponujajo primere primerov uporabe lambda izrazov. Če želite več informacij o lambda izrazih v Javi 8, si oglejte tehnični osrednji naslov Marka Reinholda za JavaOne 2013.

Lambda izrazi v matematičnem primeru

Primer, uporabljen v tem članku, je Simpsonovo pravilo iz osnovnega računa. Simpsonovo pravilo ali natančneje sestavljeno Simpsonovo pravilo je numerična tehnika integracije za približevanje določenemu integralu. Ne skrbite, če pojma a ne poznate določen integral; v resnici morate razumeti, da je Simpsonovo pravilo algoritem, ki izračuna realno število na podlagi štirih parametrov:

  • Funkcija, ki jo želimo integrirati.
  • Dve realni številki a in b ki predstavljajo končne točke intervala [a, b] na pravi številčni črti. (Upoštevajte, da mora biti zgoraj omenjena funkcija v tem intervalu neprekinjena.)
  • Celo celo število n ki določa število podintervalov. Pri izvajanju Simpsonovega pravila delimo interval [a, b] v n podintervali.

Za poenostavitev predstavitve se osredotočimo na programski vmesnik in ne na podrobnosti izvedbe. (Resnično upam, da nam bo ta pristop obšel argumente o najboljšem ali najučinkovitejšem načinu izvajanja Simpsonovega pravila, ki ni v središču tega članka.) Uporabili bomo tip dvojno za parametre a in b, in uporabili bomo tip int za parameter n. Funkcija, ki jo želite integrirati, ima en sam parameter dvojno in vrne vrednost tipa dvojno.

Prenos Prenesite primer izvorne kode C ++ za ta članek. Ustvaril John I. Moore za JavaWorld

Parametri funkcije v C ++

Za osnovo za primerjavo začnimo s specifikacijo C ++. Ko posredujem funkcijo kot parameter v jeziku C ++, običajno raje podpišem podpis parametra funkcije z uporabo a typedef. Seznam 1 prikazuje poimenovano datoteko glave C ++ simpson.h ki določa oba typedef za parameter funkcije in programski vmesnik za funkcijo C ++ z imenom integrirati. Telo funkcije za integrirati je vsebovana v datoteki izvorne kode C ++ simpson.cpp (ni prikazano) in zagotavlja izvajanje Simpsonovega pravila.

Seznam 1. Datoteka glave C ++ za Simpsonovo pravilo

 #if! definirano (SIMPSON_H) #define SIMPSON_H #include using namespace std; typedef double DoubleFunction (dvojni x); dvojna integracija (DoubleFunction f, dvojna a, dvojna b, int n) metanje (neveljaven_argument); #endif 

Klicanje integrirati je v C ++ enostaven. Kot preprost primer, predpostavimo, da ste želeli uporabiti Simpsonovo pravilo za približanje integrala sinus funkcija iz 0 do π (PI) z uporabo 30 podintervali. (Vsakdo, ki je opravil račun I, bi moral biti sposoben natančno izračunati odgovor brez pomoči kalkulatorja, kar je dober testni primer za integrirati ob predpostavki, da ste imeli vključena ustrezne datoteke glave, kot so in "simpson.h", lahko bi poklicali funkcijo integrirati kot je prikazano v seznamu 2.

Seznam 2. Klic C ++ k integraciji funkcije

 dvojni rezultat = integriraj (sin, 0, M_PI, 30); 

To je vse. V C ++ prenesete sinus deluje tako enostavno, kot da prenesete ostale tri parametre.

Še en primer

Namesto Simpsonovega pravila bi lahko prav tako enostavno uporabil metodo bisekcije (aka algoritem bisekcije) za reševanje enačbe oblike f (x) = 0. Dejansko izvorna koda tega članka vključuje preproste izvedbe tako Simpsonovega pravila kot metode bisekcije.

Prenos Prenesite primere izvorne kode Java za ta članek. Ustvaril John I. Moore za JavaWorld

Java brez lambda izrazov

Zdaj pa poglejmo, kako je mogoče v Javi določiti Simpsonovo pravilo. Ne glede na to, ali uporabljamo lambda izraze, namesto C ++ uporabljamo vmesnik Java, prikazan v seznamu 3 typedef da podate podpis funkcijskega parametra.

Seznam 3. Vmesnik Java za funkcijski parameter

 javni vmesnik DoubleFunction {javni dvojnik f (dvojni x); } 

Za izvajanje Simpsonovega pravila v Javi ustvarimo razred z imenom Simpson ki vsebuje metodo, vključiti, s štirimi parametri, podobnimi tistim, ki smo jih naredili v C ++. Kot pri mnogih samostojnih matematičnih metodah (glej na primer java.lang.Math), bomo naredili vključiti statična metoda. Metoda vključiti je določeno na naslednji način:

Seznam 4. Podpis Java za metodo integracije v razredu Simpson

 javna statična dvojna integracija (DoubleFunction df, double a, double b, int n) 

Vse, kar smo doslej naredili v Javi, je neodvisno od tega, ali bomo uporabljali lambda izraze ali ne. Glavna razlika pri lambda izrazih je v tem, kako posredujemo parametre (natančneje, kako posredujemo funkcijski parameter) v klicu metode vključiti. Najprej bom ponazoril, kako bi to počeli v različicah Jave pred različico 8; torej brez lambda izrazov. Tako kot v primeru C ++, predpostavimo, da želimo približati integralu sinus funkcija iz 0 do π (PI) z uporabo 30 podintervali.

Uporaba vzorca adapterja za funkcijo sinus

V Javi imamo izvedbo sinus funkcija na voljo v java.lang.Math, vendar z različicami Java pred Java 8 ni preprostega, neposrednega načina, kako to prenesti sinus funkcijo metode integrirati v razredu Simpson. Eden od pristopov je uporaba vzorca adapterja. V tem primeru bi napisali preprost razred adapterja, ki implementira DoubleFunction vmesnik in ga prilagodi klicu sinus , kot je prikazano v seznamu 5.

Seznam 5. Razred adapterja za metodo Math.sin

 uvoz com.softmoore.math.DoubleFunction; javni razred DoubleFunctionSineAdapter izvaja DoubleFunction {javni dvojni f (dvojni x) {return Math.sin (x); }} 

Z uporabo tega razreda adapterjev lahko zdaj pokličemo integrirati metoda pouka Simpson kot je prikazano v seznamu 6.

Seznam 6. Uporaba razreda adapterja za klicanje metode Simpson.integrate

 DoubleFunctionSineAdapter sine = novo DoubleFunctionSineAdapter (); dvojni rezultat = Simpson.integrate (sinus, 0, Math.PI, 30); 

Ustavimo se za trenutek in primerjajmo, kaj je bilo potrebno za klic integrirati v C ++ v primerjavi s tistim, kar se je zahtevalo v prejšnjih različicah Jave. S C ++ smo preprosto poklicali integrirati, podaja štiri parametre. Z Javo smo morali ustvariti nov razred vmesnika in nato narediti primerek tega razreda, da smo lahko izvedli klic. Če bi radi integrirali več funkcij, bi morali za vsako napisati razred adapterja.

Lahko bi skrajšali kodo, potrebno za klic integrirati rahlo iz dveh stavkov Java na enega z ustvarjanjem novega primerka razreda adapterja v klicu vključiti. Uporaba anonimnega razreda namesto ustvarjanja ločenega razreda adapterja bi bil še en način za nekoliko zmanjšanje celotnega napora, kot je prikazano v seznamu 7.

Seznam 7. Uporaba anonimnega razreda za klicanje metode Simpson.integrate

 DoubleFunction sineAdapter = novo DoubleFunction () {javni dvojni f (dvojni x) {return Math.sin (x); }}; dvojni rezultat = Simpson.integrate (sineAdapter, 0, Math.PI, 30); 

Brez lambda izrazov vidite v seznamu 7 približno najmanjšo količino kode, ki bi jo lahko napisali v Javi za klic integrirati metoda, vendar je še vedno precej okornejša od tiste, ki je bila potrebna za C ++. Tudi z uporabo anonimnih predavanj nisem tako zadovoljen, čeprav sem jih v preteklosti že veliko uporabljal. Sintaksa mi ni všeč in sem vedno menila, da gre za nespreten, a nujen kramp v jeziku Java.

Java z lambda izrazi in funkcionalnimi vmesniki

Zdaj pa poglejmo, kako bi lahko uporabili lambda izraze v Javi 8 za poenostavitev klica vključiti v Javi. Ker vmesnik DoubleFunction zahteva izvajanje samo ene metode, je kandidat za lambda izraze. Če vnaprej vemo, da bomo uporabljali lambda izraze, lahko vmesnik označimo z @FunctionalInterface, nov pripis za Javo 8, ki pravi, da imamo funkcijski vmesnik. Upoštevajte, da ta pripis ni obvezen, vendar nam daje dodaten pregled, da je vse skladno, podobno kot @Override v prejšnjih različicah Jave.

Sintaksa lambda izraza je seznam argumentov, zajet v oklepajih, žeton puščice (->) in funkcijsko telo. Telo je lahko bodisi blok stavkov (zaprt v oklepajih) bodisi en izraz. Seznam 8 prikazuje lambda izraz, ki implementira vmesnik DoubleFunction in se nato posreduje metodi vključiti.

Seznam 8. Uporaba lambda izraza za klicanje metode Simpson.integrate

 DoubleFunction sinus = (dvojni x) -> Math.sin (x); dvojni rezultat = Simpson.integrate (sinus, 0, Math.PI, 30); 

Upoštevajte, da nam ni bilo treba pisati razreda adapterja ali ustvariti primerka anonimnega razreda. Upoštevajte tudi, da bi lahko zgoraj zapisali v enem samem stavku, tako da nadomestimo sam lambda izraz, (dvojni x) -> Math.sin (x), za parameter sinus v drugi zgornji izjavi odpravi prvo izjavo. Zdaj se približujemo preprosti skladnji, ki smo jo imeli v C ++. Ampak počakaj! Še več je!

Ime funkcionalnega vmesnika ni del lambda izraza, vendar ga je mogoče sklepati glede na kontekst. Tip dvojno za parameter lambda izraza lahko sklepamo tudi iz konteksta. Če je v lambda izrazu samo en parameter, lahko oklepaje izpustimo. Tako lahko kodo skrajšamo na klicno metodo vključiti v eno vrstico kode, kot je prikazano na seznamu 9.

Seznam 9. Nadomestna oblika lambda izraza v klicu Simpson.integrate

 dvojni rezultat = Simpson.integrate (x -> Math.sin (x), 0, Math.PI, 30); 

Ampak počakaj! Še več jih je!

Sklici na metode v Javi 8

Druga sorodna značilnost Java 8 je nekaj, kar se imenuje a sklic na metodo, ki nam omogoča, da se poimenujemo na obstoječo metodo. Namesto lambda izrazov se lahko uporabljajo sklici na metode, če izpolnjujejo zahteve funkcionalnega vmesnika. Kot je opisano v virih, obstaja več različnih vrst referenc na metode, od katerih ima vsaka nekoliko drugačno sintakso. Za statične metode je sintaksa Classname :: methodName. Zato lahko s sklicem na metodo pokličemo vključiti metoda v Javi tako preprosto kot v C ++. Primerjajte klic Java 8, prikazan v spodnjem seznamu 10, z originalnim klicem C ++, prikazan v zgornjem seznamu 2.

Seznam 10. Uporaba sklica na metodo za klic Simpson.integrate

 dvojni rezultat = Simpson.integrate (Math :: sin, 0, Math.PI, 30);