Programmering

Java-programmering med lambda-uttrykk

I den tekniske hovedadressen for JavaOne 2013 beskrev Mark Reinhold, sjefarkitekt for Java Platform Group i Oracle, lambdauttrykk som den største enkeltoppgraderingen til Java-programmeringsmodellen. noen gang. Selv om det er mange applikasjoner for lambdauttrykk, fokuserer denne artikkelen på et spesifikt eksempel som ofte forekommer i matematiske applikasjoner; nemlig behovet for å overføre en funksjon til en algoritme.

Som en gråhåret nørd har jeg programmert på flere språk gjennom årene, og jeg har programmert mye i Java siden versjon 1.1. Da jeg begynte å jobbe med datamaskiner, var det nesten ingen som hadde en grad i informatikk. Datapersonell kom for det meste fra andre disipliner som elektroteknikk, fysikk, business og matematikk. I mitt eget tidligere liv var jeg matematiker, og det skulle derfor ikke komme som noen overraskelse at mitt første syn på en datamaskin var det som en gigantisk programmerbar kalkulator. Jeg har utvidet mitt syn på datamaskiner betydelig gjennom årene, men jeg gleder meg fortsatt over muligheten til å jobbe med applikasjoner som involverer noen aspekter av matematikk.

Mange applikasjoner i matematikk krever at en funksjon overføres som parameter til en algoritme. Eksempler fra college-algebra og grunnleggende kalkulator inkluderer å løse en ligning eller beregne integriteten til en funksjon. I over 15 år har Java vært det programmeringsspråket jeg valgte for de fleste applikasjoner, men det var det første språket jeg brukte ofte som ikke tillot meg å passere en funksjon (teknisk sett en peker eller referanse til en funksjon) som en parameter på en enkel, grei måte. Den mangelen er i ferd med å endres med den kommende utgivelsen av Java 8.

Kraften til lambdauttrykk strekker seg langt utover et engangsbruk, men å studere forskjellige implementeringer av det samme eksemplet bør gi deg en solid følelse av hvordan lambdas vil være til nytte for Java-programmene dine. I denne artikkelen vil jeg bruke et vanlig eksempel for å beskrive problemet, og deretter gi løsninger skrevet i C ++, Java før lambdauttrykk, og Java med lambdauttrykk. Legg merke til at det ikke kreves sterk bakgrunn i matematikk for å forstå og forstå de viktigste punktene i denne artikkelen.

Lære om lambdas

Lambda-uttrykk, også kjent som lukkinger, funksjonsbokstaver eller bare lambdas, beskriver et sett med funksjoner definert i Java Specification Request (JSR) 335. Mindre formelle / mer lesbare introduksjoner til lambda-uttrykk er gitt i en del av den siste versjonen av Java Tutorial og i et par artikler av Brian Goetz, "State of the lambda" og "State of the lambda: Libraries edition." Disse ressursene beskriver syntaksen til lambdauttrykk og gir eksempler på brukstilfeller der lambdauttrykk er anvendbare. For mer informasjon om lambdauttrykk i Java 8, se Mark Reinholds tekniske hovedadresse for JavaOne 2013.

Lambda-uttrykk i et matematisk eksempel

Eksemplet som brukes i denne artikkelen er Simpsons regel fra grunnleggende kalkulator. Simpson's Rule, eller nærmere bestemt Composite Simpson's Rule, er en numerisk integrasjonsteknikk for å tilnærme seg en bestemt integral. Ikke bekymre deg hvis du ikke er kjent med begrepet a bestemt integral; det du virkelig trenger å forstå er at Simpsons regel er en algoritme som beregner et reelt tall basert på fire parametere:

  • En funksjon som vi ønsker å integrere.
  • To reelle tall en og b som representerer sluttpunktene for et intervall [a, b] på reell tallinje. (Merk at funksjonen som er referert til ovenfor, skal være kontinuerlig i dette intervallet.)
  • Et jevnt heltall n som spesifiserer et antall delintervaller. Når vi implementerer Simpsons regel, deler vi intervallet [a, b] inn i n delintervaller.

For å forenkle presentasjonen, la oss fokusere på programmeringsgrensesnittet og ikke på implementeringsdetaljene. (Sannferdig, jeg håper at denne tilnærmingen vil la oss omgå argumenter om den beste eller mest effektive måten å implementere Simpsons regel, som ikke er fokus for denne artikkelen.) Vi vil bruke type dobbelt for parametere en og b, og vi vil bruke type int for parameter n. Funksjonen som skal integreres tar en enkelt parameter av typen dobbelt og en retur av verdien av typen dobbelt.

Last ned Last ned eksemplet på C ++ kildekode for denne artikkelen. Skapt av John I. Moore for JavaWorld

Funksjonsparametere i C ++

For å gi et sammenligningsgrunnlag, la oss starte med en C ++ spesifikasjon. Når jeg sender en funksjon som en parameter i C ++, foretrekker jeg vanligvis å spesifisere signaturen til funksjonsparameteren ved hjelp av a typedef. Oppføring 1 viser en C ++ headerfil med navnet simpson.h som spesifiserer både typedef for funksjonsparameteren og programmeringsgrensesnittet for en C ++ - funksjon som heter integrere. Funksjonsorganet for integrere er inneholdt i en C ++ kildekodefil med navnet simpson.cpp (ikke vist) og gir implementeringen av Simpsons regel.

Oppføring 1. C ++ header-fil for Simpsons regel

 #if! defined (SIMPSON_H) #define SIMPSON_H #include using namespace std; typedef dobbel DoubleFunction (dobbel x); dobbelt integrere (DoubleFunction f, doble a, doble b, int n) kaste (ugyldig_argument); #slutt om 

Ringer integrere er grei i C ++. Anta at du ønsket å bruke Simpsons regel for å tilnærme integralen av sinus funksjon fra 0 til π (PI) ved hjelp av 30 delintervaller. (Alle som har fullført Calculus I, skal kunne beregne svaret nøyaktig uten hjelp fra en kalkulator, noe som gjør dette til en god testtilfelle for integrere funksjon.) Forutsatt at du hadde inkludert de riktige headerfilene som og "simpson.h", vil du kunne ringe funksjon integrere som vist i oppføring 2.

Oppføring 2. C ++ kall til funksjonsintegrering

 dobbelt resultat = integrer (sin, 0, M_PI, 30); 

Det er alt det er med det. I C ++ passerer du sinus fungerer like enkelt som du passerer de tre andre parametrene.

Et annet eksempel

I stedet for Simpsons regel kunne jeg like gjerne ha brukt biseksjonsmetoden (aka Biseksjonsalgoritmen) for å løse en ligning av skjemaet f (x) = 0. Faktisk inneholder kildekoden for denne artikkelen enkle implementeringer av både Simpsons regel og biseksjonsmetoden.

Last ned Last ned eksemplene på Java-kildekoden for denne artikkelen. Skapt av John I. Moore for JavaWorld

Java uten lambdauttrykk

La oss nå se på hvordan Simpsons regel kan spesifiseres i Java. Uansett om vi bruker lambda-uttrykk eller ikke, bruker vi Java-grensesnittet vist i Listing 3 i stedet for C ++ typedef for å spesifisere signaturen til funksjonsparameteren.

Oppføring 3. Java-grensesnitt for funksjonsparameteren

 offentlig grensesnitt DoubleFunction {public double f (double x); } 

For å implementere Simpsons regel i Java oppretter vi en klasse som heter Simpson som inneholder en metode, integrere, med fire parametere som ligner på det vi gjorde i C ++. Som med mange selvstendige matematiske metoder (se for eksempel java.lang.Math), vi skal lage integrere en statisk metode. Metode integrere er spesifisert som følger:

Oppføring 4. Java-signatur for metodeintegrering i klasse Simpson

 offentlig statisk dobbel integrering (DoubleFunction df, double a, double b, int n) 

Alt vi har gjort hittil i Java er uavhengig av om vi vil bruke lambdauttrykk eller ikke. Den primære forskjellen med lambdauttrykk er i hvordan vi overfører parametere (nærmere bestemt hvordan vi overfører funksjonsparameteren) i en samtale til metode integrere. Først skal jeg illustrere hvordan dette vil bli gjort i versjoner av Java før versjon 8; dvs. uten lambdauttrykk. Som med C ++ eksempel, anta at vi vil tilnærme integralen til sinus funksjon fra 0 til π (PI) ved hjelp av 30 delintervaller.

Bruke adaptermønsteret for sinusfunksjonen

I Java har vi en implementering av sinus funksjon tilgjengelig i java.lang.Math, men med versjoner av Java før Java 8, er det ingen enkel, direkte måte å passere dette på sinus funksjon til metoden integrere i klassen Simpson. En tilnærming er å bruke adaptermønsteret. I dette tilfellet vil vi skrive en enkel adapterklasse som implementerer DoubleFunction grensesnitt og tilpasser det til å kalle sinus som vist i liste 5.

Oppføring 5. Adapterklasse for metode Math.sin

 importere com.softmoore.math.DoubleFunction; offentlig klasse DoubleFunctionSineAdapter implementerer DoubleFunction {offentlig dobbel f ​​(dobbel x) {retur Math.sin (x); }} 

Ved hjelp av denne adapterklassen kan vi nå kalle integrere metode for klassen Simpson som vist i liste 6.

Oppføring 6. Bruke adapterklassen for å kalle metoden Simpson.integrate

 DoubleFunctionSineAdapter sinus = ny DoubleFunctionSineAdapter (); dobbelt resultat = Simpson. integrer (sinus, 0, Math.PI, 30); 

La oss stoppe et øyeblikk og sammenligne det som var nødvendig for å ringe til integrere i C ++ versus det som var nødvendig i tidligere versjoner av Java. Med C ++ ringte vi ganske enkelt integrere, passerer inn de fire parametrene. Med Java måtte vi opprette en ny adapterklasse og deretter starte denne klassen for å ringe. Hvis vi ønsket å integrere flere funksjoner, måtte vi skrive en adapterklasse for hver av dem.

Vi kan forkorte koden som trengs for å ringe integrere litt fra to Java-setninger til en ved å opprette den nye forekomsten av adapterklassen i samtalen til integrere. Å bruke en anonym klasse i stedet for å lage en egen adapterklasse, vil være en annen måte å redusere den totale innsatsen litt, som vist i Listing 7.

Oppføring 7. Bruk av en anonym klasse for å kalle metoden Simpson.integrate

 DoubleFunction sineAdapter = new DoubleFunction () {public double f (double x) {return Math.sin (x); }}; dobbelt resultat = Simpson.integrate (sineAdapter, 0, Math.PI, 30); 

Uten lambdauttrykk handler det du ser på Listing 7 om den minste mengden kode du kan skrive på Java for å kalle integrere metode, men det er fortsatt mye mer tungvint enn det som var nødvendig for C ++. Jeg er heller ikke så fornøyd med å bruke anonyme klasser, selv om jeg har brukt dem mye tidligere. Jeg misliker syntaksen og har alltid sett på det som et klønete, men nødvendig hack på Java-språket.

Java med lambdauttrykk og funksjonelle grensesnitt

La oss nå se på hvordan vi kunne bruke lambda-uttrykk i Java 8 for å forenkle samtalen til integrere i Java. Fordi grensesnittet DoubleFunction krever implementering av bare en enkelt metode, det er en kandidat for lambdauttrykk. Hvis vi på forhånd vet at vi skal bruke lambdauttrykk, kan vi kommentere grensesnittet med @FunctionalInterface, en ny kommentar for Java 8 som sier at vi har en funksjonelt grensesnitt. Merk at denne kommentaren ikke er påkrevd, men den gir oss en ekstra sjekk av at alt er konsistent, i likhet med @Overstyring kommentar i tidligere versjoner av Java.

Syntaksen til et lambdauttrykk er en argumentliste i parentes, et piltegn (->), og et funksjonsorgan. Kroppen kan enten være en setningsblokk (lukket i bukseseler) eller et enkelt uttrykk. Oppføring 8 viser et lambdauttrykk som implementerer grensesnittet DoubleFunction og blir deretter overført til metode integrere.

Oppføring 8. Bruke et lambdauttrykk for å kalle metoden Simpson.integrate

 DoubleFunction sinus = (dobbel x) -> Math.sin (x); dobbelt resultat = Simpson. integrer (sinus, 0, Math.PI, 30); 

Merk at vi ikke måtte skrive adapterklassen eller opprette en forekomst av en anonym klasse. Legg også merke til at vi kunne ha skrevet ovennevnte i en enkelt uttalelse ved å erstatte selve lambdauttrykket, (dobbel x) -> Math.sin (x), for parameteren sinus i den andre uttalelsen ovenfor, eliminerer den første utsagnet. Nå kommer vi mye nærmere den enkle syntaksen vi hadde i C ++. Men vent! Det er mer!

Navnet på det funksjonelle grensesnittet er ikke en del av lambda-uttrykket, men kan utledes ut fra konteksten. Typen dobbelt for parameteren til lambdauttrykket kan også utledes fra konteksten. Til slutt, hvis det bare er en parameter i lambdauttrykket, kan vi utelate parentesene. Dermed kan vi forkorte koden for å ringe metoden integrere til en enkelt kodelinje, som vist i liste 9.

Oppføring 9. Et alternativt format for lambdauttrykk i samtale til Simpson.integrate

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

Men vent! Det er enda mer!

Metodehenvisninger i Java 8

En annen relatert funksjon i Java 8 er noe som kalles a metodehenvisning, som lar oss referere til en eksisterende metode ved navn. Metodereferanser kan brukes i stedet for lambda-uttrykk så lenge de tilfredsstiller kravene til det funksjonelle grensesnittet. Som beskrevet i ressursene, er det flere forskjellige typer referanser, hver med en litt annen syntaks. For statiske metoder er syntaksen Klassenavn :: methodName. Derfor, ved hjelp av en metodereferanse, kan vi kalle integrere metode i Java så enkelt som vi kunne i C ++. Sammenlign Java 8-anrop vist i Listing 10 nedenfor med originalt C ++ call vist i Listing 2 ovenfor.

Oppføring 10. Bruke en metodereferanse for å ringe Simpson.integrate

 dobbelt resultat = Simpson.integrate (Math :: sin, 0, Math.PI, 30); 
$config[zx-auto] not found$config[zx-overlay] not found