Programmering

Java-programmering med lambda-udtryk

I den tekniske hovedtale til JavaOne 2013 beskrev Mark Reinhold, chefarkitekt for Java Platform Group hos Oracle, lambda-udtryk som den største enkeltstående opgradering til Java-programmeringsmodellen. nogensinde. Mens der er mange applikationer til lambda-udtryk, fokuserer denne artikel på et specifikt eksempel, der ofte forekommer i matematiske applikationer; nemlig behovet for at overføre en funktion til en algoritme.

Som en gråhåret nørd har jeg programmeret på adskillige sprog gennem årene, og jeg har programmeret udførligt i Java siden version 1.1. Da jeg begyndte at arbejde med computere, havde næsten ingen en grad i datalogi. Computer fagfolk kom for det meste fra andre discipliner såsom elektroteknik, fysik, forretning og matematik. I mit eget tidligere liv var jeg matematiker, og det skulle derfor ikke komme som nogen overraskelse, at min oprindelige opfattelse af en computer var en gigantisk programmerbar lommeregner. Jeg har udvidet mit syn på computere betydeligt gennem årene, men jeg glæder mig stadig over muligheden for at arbejde på applikationer, der involverer et eller andet aspekt af matematik.

Mange applikationer i matematik kræver, at en funktion sendes som parameter til en algoritme. Eksempler fra college-algebra og grundlæggende beregning inkluderer løsning af en ligning eller beregning af en integrals funktion. I over 15 år har Java været mit valgte programmeringssprog til de fleste applikationer, men det var det første sprog, jeg brugte ofte, som ikke tillod mig at passere en funktion (teknisk set en markør eller henvisning til en funktion) som en parameter på en enkel, ligetil måde. Denne mangel er ved at ændre sig med den kommende udgivelse af Java 8.

Kraften i lambda-udtryk strækker sig langt ud over en enkelt brugssag, men at studere forskellige implementeringer af det samme eksempel burde give dig en solid fornemmelse af, hvordan lambdas vil gavne dine Java-programmer. I denne artikel vil jeg bruge et almindeligt eksempel til at beskrive problemet og derefter levere løsninger skrevet i C ++, Java før lambda-udtryk og Java med lambda-udtryk. Bemærk, at der ikke kræves en stærk baggrund inden for matematik for at forstå og forstå de vigtigste punkter i denne artikel.

At lære om lambdas

Lambda-udtryk, også kendt som lukninger, funktionsbogstaver eller simpelthen lambdas, beskriver et sæt funktioner defineret i Java Specification Request (JSR) 335. Mindre formelle / mere læsbare introduktioner til lambda-udtryk findes i et afsnit i den nyeste version af Java Tutorial og i et par artikler af Brian Goetz, "State of the lambda" og "State of the lambda: Libraries edition." Disse ressourcer beskriver syntaks for lambda-udtryk og giver eksempler på brugstilfælde, hvor lambda-udtryk er anvendelige. For mere om lambda-udtryk i Java 8, se Mark Reinholds tekniske hovedtale til JavaOne 2013.

Lambda-udtryk i et matematisk eksempel

Eksemplet, der bruges i denne artikel, er Simpsons regel fra grundlæggende beregning. Simpson's Rule, eller mere specifikt Composite Simpson's Rule, er en numerisk integrationsteknik for at tilnærme en bestemt integral. Bare rolig, hvis du ikke er bekendt med begrebet a bestemt integral; hvad du virkelig har brug for at forstå er, at Simpsons regel er en algoritme, der beregner et reelt tal baseret på fire parametre:

  • En funktion, som vi ønsker at integrere.
  • To reelle tal -en og b der repræsenterer slutpunkterne for et interval [a, b] på den rigtige talelinje. (Bemærk, at ovennævnte funktion skal være kontinuerlig i dette interval.)
  • Et lige heltal n der specificerer et antal underintervaller. Ved implementering af Simpsons regel deler vi intervallet [a, b] ind i n underintervaller.

For at forenkle præsentationen skal vi fokusere på programmeringsgrænsefladen og ikke på implementeringsdetaljerne. (Sandfærdigt håber jeg, at denne tilgang vil lade os omgå argumenter om den bedste eller mest effektive måde at implementere Simpsons regel på, som ikke er fokus for denne artikel.) Vi vil bruge typen dobbelt for parametre -en og b, og vi bruger type int for parameter n. Funktionen, der skal integreres, tager en enkelt parameter af typen dobbelt og returnerer en værdi af typen dobbelt.

Download Download C ++ kildekodeeksemplet til denne artikel. Oprettet af John I. Moore til JavaWorld

Funktionsparametre i C ++

For at give et sammenligningsgrundlag, lad os starte med en C ++ - specifikation. Når jeg sender en funktion som parameter i C ++, foretrækker jeg normalt at specificere funktionsparameterens signatur ved hjælp af a typedef. Fortegnelse 1 viser en C ++ headerfil med navnet simpson.h der specificerer både typedef til funktionsparameteren og programmeringsgrænsefladen til en C ++ - funktion med navnet integrere. Funktionsorganet til integrere er indeholdt i en C ++ kildekodefil med navnet simpson.cpp (ikke vist) og giver implementeringen af ​​Simpsons regel.

Notering 1. C ++ header-fil til Simpsons regel

 #if! defineret (SIMPSON_H) #definer SIMPSON_H #include ved hjælp af namespace std; typedef dobbelt DoubleFunction (dobbelt x); dobbelt integrer (DoubleFunction f, dobbelt a, dobbelt b, int n) kast (ugyldigt_argument); #Afslut Hvis 

Ringer integrere er ligetil i C ++. Antag som et simpelt eksempel, at du ville bruge Simpsons regel til at tilnærme integralen af sinus funktion fra 0 til π (PI) ved brug af 30 underintervaller. (Enhver, der har gennemført Calculus I, skal være i stand til at beregne svaret nøjagtigt uden hjælp fra en lommeregner, hvilket gør dette til en god testcase for integrere funktion.) Under forudsætning af at du havde inkluderet de korrekte headerfiler som f.eks og "simpson.h", ville du være i stand til at ringe til funktionen integrere som vist i liste 2.

Liste 2. C ++ kald til funktionsintegrering

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

Det er alt der er ved det. I C ++ passerer du sinus fungerer så let som du passerer de andre tre parametre.

Et andet eksempel

I stedet for Simpsons regel kunne jeg lige så let have brugt Bisection-metoden (aka Bisection Algorithm) til løsning af en ligning af formen f (x) = 0. Faktisk indeholder kildekoden til denne artikel enkle implementeringer af både Simpsons regel og gennemskæringsmetoden.

Download Download Java-kildekodeeksemplerne til denne artikel. Oprettet af John I. Moore til JavaWorld

Java uden lambda-udtryk

Lad os nu se på, hvordan Simpsons regel kan specificeres i Java. Uanset om vi bruger lambda-udtryk eller ej, bruger vi Java-interface vist i Listing 3 i stedet for C ++ typedef for at specificere signaturen for funktionsparameteren.

Liste 3. Java-interface til funktionsparameteren

 offentlig grænseflade DoubleFunction {offentlig dobbelt f (dobbelt x); } 

For at implementere Simpsons regel i Java opretter vi en klasse med navnet Simpson der indeholder en metode, integrere, med fire parametre svarende til hvad vi gjorde i C ++. Som med mange selvstændige matematiske metoder (se f.eks. java.lang.Math), vi laver integrere en statisk metode. Metode integrere er specificeret som følger:

Liste 4. Java-signatur til metodeintegrering i klasse Simpson

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

Alt, hvad vi hidtil har gjort i Java, er uafhængig af, om vi vil bruge lambda-udtryk eller ej. Den primære forskel med lambda-udtryk er i, hvordan vi videregiver parametre (mere specifikt, hvordan vi videregiver funktionsparameteren) i en opfordring til metode integrere. Først illustrerer jeg, hvordan dette ville blive gjort i versioner af Java før version 8; dvs. uden lambda-udtryk. Som med C ++ -eksemplet antager vi, at vi vil tilnærme integralen af sinus funktion fra 0 til π (PI) ved brug af 30 underintervaller.

Brug af adaptermønsteret til sinusfunktionen

I Java har vi en implementering af sinus funktion tilgængelig i java.lang.Math, men med versioner af Java før Java 8 er der ingen enkel, direkte måde at videregive dette på sinus funktion til metoden integrere i klassen Simpson. En tilgang er at bruge adaptermønsteret. I dette tilfælde vil vi skrive en simpel adapterklasse, der implementerer DoubleFunction interface og tilpasser det til at kalde sinus funktion, som vist i liste 5.

Notering 5. Adapterklasse til metode Math.sin

 import com.softmoore.math.DoubleFunction; offentlig klasse DoubleFunctionSineAdapter implementerer DoubleFunction {offentlig dobbelt f (dobbelt x) {return Math.sin (x); }} 

Ved hjælp af denne adapterklasse kan vi nu kalde integrere metode til klasse Simpson som vist i liste 6.

Liste 6. Brug af adapterklassen til at kalde metoden Simpson.integrate

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

Lad os stoppe et øjeblik og sammenligne, hvad der var nødvendigt for at foretage opkaldet integrere i C ++ versus hvad der kræves i tidligere versioner af Java. Med C ++ ringede vi simpelthen integrere, passerer de fire parametre. Med Java måtte vi oprette en ny adapterklasse og derefter starte denne klasse for at foretage opkaldet. Hvis vi ville integrere flere funktioner, ville vi skulle skrive en adapterklasse til hver af dem.

Vi kunne forkorte den kode, der var nødvendig for at ringe integrere lidt fra to Java-udsagn til en ved at oprette den nye forekomst af adapterklassen inden for opkaldet til integrere. Brug af en anonym klasse i stedet for at oprette en separat adapterklasse ville være en anden måde at reducere den samlede indsats lidt som vist i liste 7.

Fortegnelse 7. Brug af en anonym klasse til at kalde metoden Simpson.integrate

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

Uden lambda-udtryk handler det, du ser i Listing 7 om den mindste mængde kode, som du kunne skrive i Java for at kalde integrere metode, men det er stadig meget mere besværligt, end hvad der kræves for C ++. Jeg er heller ikke så tilfreds med at bruge anonyme klasser, selvom jeg tidligere har brugt dem meget. Jeg kan ikke lide syntaksen og har altid betragtet det som et klodset, men nødvendigt hack på Java-sproget.

Java med lambda-udtryk og funktionelle grænseflader

Lad os nu se på, hvordan vi kunne bruge lambda-udtryk i Java 8 til at forenkle opkaldet til integrere i Java. Fordi grænsefladen DoubleFunction kræver kun implementering af en enkelt metode, det er en kandidat til lambda-udtryk. Hvis vi på forhånd ved, at vi skal bruge lambda-udtryk, kan vi kommentere grænsefladen med @FunktionelInterface, en ny kommentar til Java 8, der siger, at vi har en funktionel grænseflade. Bemærk, at denne kommentar ikke er påkrævet, men det giver os en ekstra kontrol, at alt er konsistent, svarende til @Override kommentar i tidligere versioner af Java.

Syntaksen for et lambda-udtryk er en argumenteliste i parentes, et piletoken (->) og et funktionsorgan. Kroppen kan enten være en udsagnsblok (lukket i seler) eller et enkelt udtryk. Liste 8 viser et lambda-udtryk, der implementerer grænsefladen DoubleFunction og overføres derefter til metode integrere.

Liste 8. Brug et lambda-udtryk til at kalde metoden Simpson.integrate

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

Bemærk, at vi ikke behøvede at skrive adapterklassen eller oprette en forekomst af en anonym klasse. Bemærk også, at vi kunne have skrevet ovenstående i en enkelt erklæring ved at erstatte selve lambda-udtrykket, (dobbelt x) -> Math.sin (x), for parameteren sinus i den anden erklæring ovenfor, eliminerer den første erklæring. Nu kommer vi meget tættere på den enkle syntaks, som vi havde i C ++. Men vent! Der er mere!

Navnet på den funktionelle grænseflade er ikke en del af lambda-udtrykket, men kan udledes ud fra sammenhængen. Typen dobbelt for parameteren for lambda-ekspression kan også udledes fra sammenhængen. Endelig, hvis der kun er en parameter i lambda-udtrykket, kan vi udelade parenteserne. Således kan vi forkorte koden til opkaldsmetoden integrere til en enkelt kodelinje, som vist i liste 9.

Liste 9. Et alternativt format til lambda-udtryk i kald til Simpson.integrate

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

Men vent! Der er endnu mere!

Metodehenvisninger i Java 8

En anden relateret funktion i Java 8 er noget, der kaldes a metodehenvisning, som giver os mulighed for at henvise til en eksisterende metode ved navn. Metodehenvisninger kan bruges i stedet for lambda-udtryk, så længe de opfylder kravene i den funktionelle grænseflade. Som beskrevet i ressourcerne er der flere forskellige slags referencer, hver med en lidt anden syntaks. For statiske metoder er syntaksen Klassenavn :: methodName. Derfor kan vi ved hjælp af en metodehenvisning kalde integrere metode i Java så enkelt som vi kunne i C ++. Sammenlign Java 8-opkald vist i liste 10 nedenfor med det originale C ++-opkald vist i liste 2 ovenfor.

Liste 10. Brug af en metodereference til at kalde Simpson.integrate

 dobbelt resultat = Simpson.integrate (Math :: sin, 0, Math.PI, 30);