JavaOne 2013 tehniskajā galvenajā adresē Marks Reinholds, Oracle Java platformas grupas galvenais arhitekts, lambda izteiksmes aprakstīja kā lielāko Java programmēšanas modeļa jauninājumu kādreiz. Lai gan lambda izteiksmēm ir daudz lietojumu, šis raksts koncentrējas uz konkrētu piemēru, kas bieži sastopams matemātiskās lietojumprogrammās; proti, nepieciešamība nodot funkciju algoritmam.
Kā pelēks matains geek esmu gadu gaitā ieprogrammējis daudzās valodās, un kopš 1.1 versijas esmu plaši ieprogrammējis Java. Kad sāku strādāt ar datoriem, gandrīz nevienam nebija grāda datorzinātnēs. Datoru profesionāļi pārsvarā ieradās no citām disciplīnām, piemēram, elektrotehnikas, fizikas, biznesa un matemātikas. Savā bijušajā dzīvē es biju matemātiķis, un tāpēc nevajadzētu pārsteigt, ka mans sākotnējais skats uz datoru bija milzīgs programmējams kalkulators. Gadu gaitā esmu ievērojami paplašinājis savu viedokli par datoriem, taču joprojām atzinīgi vērtēju iespēju strādāt pie lietojumprogrammām, kas saistītas ar kādu matemātikas aspektu.
Daudzās matemātikas lietojumprogrammās ir jānodod funkcija kā parametrs algoritmam. Koledžas algebras un pamata aprēķina piemēri ietver vienādojuma atrisināšanu vai funkcijas integrāla aprēķināšanu. Vairāk nekā 15 gadus Java ir bijusi mana izvēlētā programmēšanas valoda lielākajai daļai lietojumprogrammu, taču tā bija pirmā bieži izmantotā valoda, kas neļāva man nodot funkciju (tehniski rādītāju vai atsauci uz funkciju) kā parametru vienkāršā, tiešā veidā. Šis trūkums drīz mainīsies līdz ar gaidāmo Java 8 izlaišanu.
Lambda izteiksmju spēks pārsniedz vienreizējas lietošanas gadījumus, taču, pētot dažādas viena un tā paša piemēra realizācijas, jums vajadzētu pārliecināties, kā lambdas dos labumu jūsu Java programmām. Šajā rakstā es izmantošu izplatītu piemēru, lai palīdzētu aprakstīt problēmu, pēc tam sniegšu risinājumus, kas rakstīti C ++, Java pirms lambda izteicieniem un Java ar lambda izteiksmēm. Ņemiet vērā, ka, lai saprastu un novērtētu šī raksta galvenos punktus, nav nepieciešama spēcīga matemātikas pieredze.
Mācīšanās par lambdas
Lambda izteiksmes, kas pazīstamas arī kā slēgšanas, funkciju literāļi vai vienkārši lambdas, apraksta funkciju specifikāciju, kas definēta Java specifikācijas pieprasījumā (JSR) 335. Mazāk formāli / lasāmāki lambda izteicienu ievadi ir sniegti jaunākās versijas sadaļā. Java apmācība un pāris Braiena Geca rakstu "Lambda stāvoklis" un "Lambda stāvoklis: Libraries izdevums". Šie resursi apraksta lambda izteicienu sintaksi un sniedz piemērus lietošanas gadījumiem, kad lambda izteiksmes ir piemērojamas. Lai uzzinātu vairāk par lambda izteiksmēm Java 8, skatiet Marka Reinholda tehnisko galveno adresi JavaOne 2013.
Lambda izteicieni matemātiskā piemērā
Šajā rakstā izmantotais piemērs ir Simpsona likums no pamata aprēķina. Simpsona likums vai, precīzāk, saliktais Simpsona noteikums, ir skaitliska integrācijas tehnika, lai tuvinātu noteiktu integrālu. Neuztraucieties, ja jums nav pazīstams jēdziens a noteikts neatņemams; kas jums patiešām jāsaprot, ir tas, ka Simpsona likums ir algoritms, kas aprēķina reālu skaitli, pamatojoties uz četriem parametriem:
- Funkcija, kuru mēs vēlamies integrēt.
- Divi reālie skaitļi
a
unb
kas apzīmē intervāla galapunktus[a, b]
reālā skaitļa līnijā. (Ņemiet vērā, ka iepriekš minētajai funkcijai šajā intervālā jābūt nepārtrauktai.) - Vienmērīgs vesels skaitlis
n
kas norāda vairākus apakšintervālus. Īstenojot Simpsona likumu, mēs dalām intervālu[a, b]
vērān
apakšintervali.
Lai vienkāršotu prezentāciju, pievērsīsimies programmēšanas saskarnei, nevis ieviešanas detaļām. (Patiesi, es ceru, ka šī pieeja ļaus mums apiet argumentus par labāko vai visefektīvāko veidu, kā ieviest Simpsona likumu, kas nav šī raksta uzmanības centrā.) Mēs izmantosim veidu dubultā
parametriem a
un b
, un mēs izmantosim veidu int
parametram n
. Integrējamajai funkcijai būs viens tipa parametrs dubultā
un atgriež veida vērtību dubultā
.
Funkcijas parametri C ++
Lai nodrošinātu salīdzināšanas pamatu, sāksim ar C ++ specifikāciju. Pārejot funkciju kā parametru C ++, es parasti dodu priekšroku norādīt parametra parakstu, izmantojot a typedef
. 1. sarakstā tiek parādīts C ++ galvenes fails ar nosaukumu simpson.h
kas norāda gan typedef
funkcijas parametram un C ++ funkcijas nosauktajai programmēšanas saskarnei integrēt
. Funkcijas ķermenis integrēt
ir ietverts C ++ avota koda failā ar nosaukumu simpson.cpp
(nav parādīts) un nodrošina Simpson's Rule ieviešanu.
Saraksts 1. C ++ galvenes fails Simpsona likumam
#if! definēts (SIMPSON_H) #define SIMPSON_H #iekļauj, izmantojot vārdu vietas std; typedef double DoubleFunction (double x); dubultā integrācija (DoubleFunction f, double a, double b, int n) throw (invalid_argument); #endif
Zvanīšana integrēt
ir vienkāršs C ++. Pieņemsim, ka kā vienkāršu piemēru vēlaties izmantot Simpsona likumu, lai tuvinātu sinusa funkcija no 0
līdz π (PI
) izmantojot 30
apakšintervali. (Ikvienam, kurš ir pabeidzis Calculus I, vajadzētu būt iespējai precīzi aprēķināt atbildi bez kalkulatora palīdzības, padarot to par labu testa gadījumu integrēt
funkcija.) Pieņemot, ka jums bija iekļauts pareizi galvenes faili, piemēram, un
"simpson.h"
, jūs varētu izsaukt funkciju integrēt
kā parādīts 2. sarakstā.
Saraksts 2. C ++ izsaukums, lai integrētu funkciju
divkāršs rezultāts = integrēt (grēks, 0, M_PI, 30);
Tas ir viss, kas tam ir. Programmā C ++ jūs nokārtojat sinusa darbojas tikpat viegli, kā jūs nododat pārējos trīs parametrus.
Vēl viens piemērs
Simpsona noteikuma vietā es tikpat viegli būtu varējis izmantot Bisection metodi (aka Bisection Algorithm) formas vienādojuma atrisināšanai f (x) = 0. Faktiski šī raksta avota kods ietver vienkāršu gan Simpsona likuma, gan dalīšanas metodes ieviešanu.
Lejupielādēt Lejupielādējiet šī raksta Java pirmkodu piemērus. Izveidoja Džons I. Mūrs programmai JavaWorldJava bez lambda izteicieniem
Tagad aplūkosim, kā Simpsona likums varētu tikt norādīts Java. Neatkarīgi no tā, vai mēs izmantojam lambda izteicienus, C ++ vietā mēs izmantojam Java saskarni, kas parādīta 3. sarakstā typedef
lai norādītu funkcijas parametra parakstu.
Saraksts 3. Java interfeiss funkcijas parametram
publiskā saskarne DoubleFunction {public double f (double x); }
Lai ieviestu Simpsona likumu Java, mēs izveidojam klasi ar nosaukumu Simpsons
kas satur metodi, integrēt
, ar četriem parametriem, kas ir līdzīgi tam, ko mēs darījām C ++. Tāpat kā ar daudzām pašpietiekamām matemātiskām metodēm (sk., Piemēram, java.lang.Math
), mēs darīsim integrēt
statiska metode. Metode integrēt
ir noteikts šādi:
Saraksts 4. Java paraksts metodes integrēšanai Simpsona klasē
publiskā statiskā dubultā integrācija (DoubleFunction df, double a, double b, int n)
Viss, ko līdz šim esam darījuši Java valodā, nav atkarīgs no tā, vai izmantosim lambda izteicienus. Galvenā atšķirība ar lambda izteiksmēm ir tajā, kā mēs nododam parametrus (precīzāk, kā mēs nododam funkcijas parametru) aicinājumā uz metodi integrēt
. Vispirms es ilustrēšu, kā tas tiktu darīts Java versijās pirms 8. versijas; i., bez lambda izteicieniem. Tāpat kā ar C ++ piemēru, pieņemsim, ka mēs vēlamies tuvināt sinusa funkcija no 0
līdz π (PI
) izmantojot 30
apakšintervali.
Adaptera modeļa izmantošana sinusa funkcijai
Java mums ir ieviests sinusa funkcija pieejama java.lang.Math
, bet ar Java versijām pirms Java 8 nav vienkārša, tieša veida, kā to nodot sinusa metodi integrēt
klasē Simpsons
. Viena pieeja ir izmantot Adapter modeli. Šajā gadījumā mēs uzrakstīsim vienkāršu adapteru klasi, kas ievieš DoubleFunction
interfeisu un pielāgo to, lai izsauktu sinusa funkciju, kā parādīts 5. sarakstā.
Saraksts 5. Adaptera klase metodei Math.sin
importēt com.softmoore.math.DoubleFunction; publiskā klase DoubleFunctionSineAdapter īsteno DoubleFunction {public double f (double x) {return Math.sin (x); }}
Izmantojot šo adapteru klasi, tagad mēs varam izsaukt integrēt
klases metode Simpsons
kā parādīts 6. sarakstā.
Saraksts 6. Adaptera klases izmantošana, lai izsauktu metodi Simpson.integrate
DoubleFunctionSineAdapter sine = jauns DoubleFunctionSineAdapter (); divkāršs rezultāts = Simpson.integrate (sine, 0, Math.PI, 30);
Pārtrauksim brīdi un salīdzināsim to, kas bija nepieciešams, lai piezvanītu integrēt
C ++ versijā salīdzinājumā ar iepriekšējās Java versijās prasīto. Ar C ++ mēs vienkārši piezvanījām integrēt
, ievadot četrus parametrus. Izmantojot Java, mums bija jāizveido jauna adaptera klase un pēc tam jāpieliek šī klase, lai veiktu zvanu. Ja mēs vēlētos integrēt vairākas funkcijas, mums katrai no tām būs jāuzraksta adaptera klase.
Mēs varētu saīsināt kodu, kas nepieciešams, lai piezvanītu integrēt
nedaudz no diviem Java paziņojumiem uz vienu, izveidojot jauno adapteru klases instanci zvanā integrēt
. Anonīmas klases izmantošana, nevis atsevišķas adapteru klases izveidošana būtu vēl viens veids, kā nedaudz samazināt kopējo piepūli, kā parādīts 7. sarakstā.
Saraksts 7. Anonīmās klases izmantošana, lai izsauktu metodi Simpson.integrate
DoubleFunction sineAdapter = new DoubleFunction () {public double f (double x) {return Math.sin (x); }}; dubultā rezultāts = Simpson.integrate (sineAdapter, 0, Math.PI, 30);
Bez lambda izteiksmēm 7. sarakstā redzamais ir vismazākais koda daudzums, ko jūs varētu rakstīt Java valodā, lai izsauktu integrēt
metodi, taču tā joprojām ir daudz apgrūtinošāka nekā tas, kas tika prasīts C ++. Es arī neesmu tik apmierināta ar anonīmu nodarbību izmantošanu, lai gan agrāk tās esmu daudz izmantojusi. Man nepatīk sintakse un vienmēr to esmu uzskatījis par neveiklu, bet nepieciešamu uzlaušanu Java valodā.
Java ar lambda izteiksmēm un funkcionālajām saskarnēm
Tagad apskatīsim, kā mēs varētu izmantot lambda izteicienus Java 8, lai vienkāršotu zvanu uz integrēt
Java valodā. Jo interfeiss DoubleFunction
nepieciešama tikai vienas metodes ieviešana, tā var pretendēt uz lambda izteicieniem. Ja mēs iepriekš zinām, ka izmantosim lambda izteicienus, mēs varam anotēt saskarni ar @ Funkcionālā saskarne
, jauna Java 8 anotācija, kurā teikts, ka mums ir funkcionālā saskarne. Ņemiet vērā, ka šī anotācija nav nepieciešama, taču tā dod mums papildu pārbaudi, vai viss ir konsekvents, līdzīgs @ Pārvarēt
anotācija Java iepriekšējās versijās.
Lambda izteiksmes sintakse ir argumentu saraksts, kas ir iekavās, bultiņas marķieris (->
) un funkcijas ķermenis. Pamatteksts var būt vai nu paziņojuma bloks (ievietots iekavās), vai arī viena izteiksme. 8. saraksts parāda lambda izteiksmi, kas īsteno saskarni DoubleFunction
un pēc tam tiek nodota metodei integrēt
.
8. saraksta izmantošana, lai izsauktu metodi Simpson.integrate
DoubleFunction sinusā = (dubultā x) -> Math.sin (x); divkāršs rezultāts = Simpson.integrate (sine, 0, Math.PI, 30);
Ņemiet vērā, ka mums nebija jāraksta adaptera klase vai jāizveido anonīmas klases eksemplārs. Ņemiet vērā arī to, ka mēs varējām ierakstīt iepriekš minēto vienā paziņojumā, aizstājot pašu lambda izteicienu, (dubultā x) -> Math.sin (x)
, parametram sinusa
otrajā paziņojumā iepriekš, izslēdzot pirmo paziņojumu. Tagad mēs esam daudz tuvāk vienkāršajai sintaksei, kas mums bija C ++. Bet pagaidi! Tur ir vairāk!
Funkcionālās saskarnes nosaukums nav daļa no lambda izteiksmes, bet to var secināt, pamatojoties uz kontekstu. Veids dubultā
par lambda izteiksmes parametru var secināt arī no konteksta. Visbeidzot, ja lambda izteiksmē ir tikai viens parametrs, tad iekavas var izlaist. Tādējādi mēs varam saīsināt kodu, lai izsauktu metodi integrēt
uz vienu koda rindiņu, kā parādīts 9. sarakstā.
Saraksts 9. Alternatīvs lambda izteiksmes formāts zvanam uz Simpson.integrate
dubults rezultāts = Simpson.integrate (x -> Math.sin (x), 0, Math.PI, 30);
Bet pagaidi! Tur ir vēl vairāk!
Metodes atsauces Java 8
Vēl viena saistīta Java 8 iezīme ir tā sauktā a metodes atsauce, kas ļauj mums atsaukties uz esošu metodi pēc nosaukuma. Metodes atsauces var izmantot lambda izteicienu vietā, ja vien tās atbilst funkcionālās saskarnes prasībām. Kā aprakstīts resursos, ir vairākas dažādu veidu atsauces uz metodēm, katrai no tām ir nedaudz atšķirīga sintakse. Statiskām metodēm sintakse ir Klases nosaukums :: methodName
. Tāpēc, izmantojot metodes atsauci, mēs varam izsaukt integrēt
metodi Java, kā vienkārši C ++. Salīdziniet Java 8 zvanu, kas parādīts zemāk 10. sarakstā, ar sākotnējo C ++ zvanu, kas parādīts iepriekš 2. sarakstā.
Saraksts 10. Izmantojot metodes atsauci, lai izsauktu Simpson.integrate
dubultā rezultāts = Simpson.integrate (Math :: grēks, 0, Math.PI, 30);