Velkommen tilbake til denne todelte opplæringen som introduserer funksjonell programmering i Java-sammenheng. I funksjonell programmering for Java-utviklere, del 1, brukte jeg JavaScript-eksempler for å komme i gang med fem funksjonelle programmeringsteknikker: rene funksjoner, høyere ordensfunksjoner, lat evaluering, nedleggelser og karri. Å presentere disse eksemplene i JavaScript tillot oss å fokusere på teknikkene i en enklere syntaks uten å komme inn på Java's mer komplekse funksjonelle programmeringsfunksjoner.
I del 2 vil vi se på de teknikkene ved hjelp av Java-kode som er forhåndsdatert Java 8. Som du ser er denne koden funksjonell, men den er ikke lett å skrive eller lese. Du vil også bli introdusert for de nye funksjonelle programmeringsfunksjonene som var fullt integrert i Java-språket i Java 8; nemlig lambdas, referanser, funksjonelle grensesnitt og Streams API.
Gjennom denne opplæringen vil vi se på eksempler fra del 1 for å se hvordan JavaScript- og Java-eksemplene sammenlignes. Du vil også se hva som skjer når jeg oppdaterer noen av pre-Java 8 eksemplene med funksjonelle språkfunksjoner som lambdas og metodereferanser. Til slutt inkluderer denne opplæringen en praktisk øvelse designet for å hjelpe deg øve på funksjonell tenking, som du vil gjøre ved å transformere et stykke objektorientert Java-kode til dets funksjonelle ekvivalent.
last ned Få koden Last ned kildekoden for eksempel applikasjoner i denne opplæringen. Skapt av Jeff Friesen for JavaWorld.Funksjonell programmering med Java
Mange utviklere er ikke klar over det, men det var mulig å skrive funksjonelle programmer i Java før Java 8. For å ha et godt avrundet syn på funksjonell programmering i Java, la oss raskt gå gjennom funksjonelle programmeringsfunksjoner som er forut for Java 8. Når du Har du dem nede, vil du sannsynligvis ha større forståelse for hvordan nye funksjoner introdusert i Java 8 (som lambdas og funksjonelle grensesnitt) har forenklet Java tilnærming til funksjonell programmering.
Grenser for Java's støtte for funksjonell programmering
Selv med funksjonelle programmeringsforbedringer i Java 8, er Java fortsatt et viktig, objektorientert programmeringsspråk. Det mangler rekkeviddetyper og andre funksjoner som vil gjøre det mer funksjonelt. Java er også hindret av nominativ typing, som er forutsetningen om at hver type må ha et navn. Til tross for disse begrensningene, har utviklere som omfavner Javas funksjonelle funksjoner fortsatt fordeler av å kunne skrive mer kortfattet, gjenbrukbar og lesbar kode.
Funksjonell programmering før Java 8
Anonyme indre klasser sammen med grensesnitt og nedleggelser er tre eldre funksjoner som støtter funksjonell programmering i eldre versjoner av Java:
- Anonyme indre klasser lar deg overføre funksjonalitet (beskrevet av grensesnitt) til metoder.
- Funksjonelle grensesnitt er grensesnitt som beskriver en funksjon.
- Stengninger lar deg få tilgang til variabler i deres ytre omfang.
I avsnittene som følger vil vi se på de fem teknikkene introdusert i del 1, men ved hjelp av Java-syntaks. Du vil se hvordan hver av disse funksjonelle teknikkene var mulig før Java 8.
Skrive rene funksjoner i Java
Oppføring 1 presenterer kildekoden til et eksempel på et program, DaysInMonth
, som er skrevet ved hjelp av en anonym indre klasse og et funksjonelt grensesnitt. Denne applikasjonen demonstrerer hvordan du skriver en ren funksjon, som var oppnåelig i Java lenge før Java 8.
Oppføring 1. En ren funksjon i Java (DaysInMonth.java)
grensesnitt Funksjon {R gjelder (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} [måned]; }}; System.out.printf ("April:% d% n", dim.apply (3)); System.out.printf ("August:% d% n", dim.apply (7)); }}
Generisk Funksjon
grensesnitt i liste 1 beskriver en funksjon med en enkelt parameter av typen T
og en returtype av typen R
. De Funksjon
grensesnitt erklærer et R gjelder (T t)
metode som bruker denne funksjonen til det gitte argumentet.
De hoved()
metoden instantierer en anonym indre klasse som implementerer Funksjon
grensesnitt. De søke om()
metoden avkasser måned
og bruker den til å indeksere en rekke dager-i-måned-heltall. Heltallet ved denne indeksen returneres. (Jeg ignorerer skuddår for enkelhet.)
hoved()
neste utfører denne funksjonen to ganger ved å påkalle søke om()
å returnere dagstallene for månedene april og august. Disse tellingene skrives deretter ut.
Vi har klart å lage en funksjon, og en ren funksjon på det! Husk at en ren funksjon avhenger bare av argumentene og ingen ytre tilstand. Det er ingen bivirkninger.
Sammensett oppføring 1 som følger:
javac DaysInMonth.java
Kjør den resulterende applikasjonen som følger:
java DaysInMonth
Du bør følge følgende utdata:
April: 30. august: 31.
Skrive funksjoner av høyere orden i Java
Deretter vil vi se på høyere ordensfunksjoner, også kjent som førsteklasses funksjoner. Husk at a høyere ordensfunksjon mottar funksjonsargumenter og / eller returnerer et funksjonsresultat. Java forbinder en funksjon med en metode, som er definert i en anonym indre klasse. En forekomst av denne klassen sendes til eller returneres fra en annen Java-metode som fungerer som høyere ordensfunksjon. Følgende filorienterte kodefragment demonstrerer overføring av en funksjon til en høyere ordensfunksjon:
File [] txtFiles = new File ("."). ListFiles (new FileFilter () {@Override public boolean accept (File pathname) {return pathname.getAbsolutePath (). EndsWith ("txt");}});
Dette kodefragmentet passerer en funksjon basert på java.io.FileFilter
funksjonelt grensesnitt til java.io. fil
klassen File [] listFiles (FileFilter filter)
metode, og ba den bare returnere filene med tekst
utvidelser.
Oppføring 2 viser en annen måte å jobbe med høyere ordensfunksjoner i Java. I dette tilfellet overfører koden en komparatorfunksjon til a sortere()
høyere ordens funksjon for en stigende ordens sortering, og en andre komparatorfunksjon til sortere()
for en sortering etter fallende orden.
Oppføring 2. En høyere ordensfunksjon i Java (Sort.java)
importere java.util.Comparator; public class Sorter {public static void main (String [] args) {String [] innerplanets = {"Mercury", "Venus", "Earth", "Mars"}; dump (indre planeter); sorter (innerplanets, new Comparator () {@Override public int compare (String e1, String e2) {return e1.compareTo (e2);}}); dump (indre planeter); sorter (innerplanets, new Comparator () {@Override public int compare (String e1, String e2) {return e2.compareTo (e1);}}); dump (indre planeter); } statisk ugyldig dump (T [] array) {for (T element: array) System.out.println (element); System.out.println (); } statisk tomromssortering (T [] array, Comparator cmp) {for (int pass = 0; pass sende; i--) if (cmp.compare (array [i], array [pass]) <0) swap (array, i, pass); } statisk tomrombytte (T [] array, int i, int j) {T temp = array [i]; array [i] = array [j]; matrise [j] = temp; }}
Oppføring 2 importerer java.util.Comparator
funksjonelt grensesnitt, som beskriver en funksjon som kan utføre en sammenligning på to objekter av vilkårlig, men identisk type.
To viktige deler av denne koden er sortere()
metoden (som implementerer Bubble Sort-algoritmen) og sortere()
påkallelser i hoved()
metode. Selv om sortere()
er langt fra å være funksjonell, viser den en høyere ordensfunksjon som mottar en funksjon - komparatoren - som et argument. Den utfører denne funksjonen ved å påkalle den sammenligne()
metode. To forekomster av denne funksjonen sendes i to sortere()
ringer inn hoved()
.
Sammensett oppføring 2 som følger:
javac Sort.java
Kjør den resulterende applikasjonen som følger:
java Sorter
Du bør følge følgende utdata:
Merkur Venus Jorden Mars Jord Mars Merkur Venus Venus Merkur Mars Jorden
Lat evaluering i Java
Lat evaluering er en annen funksjonell programmeringsteknikk som ikke er ny for Java 8. Denne teknikken forsinker evalueringen av et uttrykk til verdien er nødvendig. I de fleste tilfeller evaluerer Java ivrig et uttrykk som er bundet til en variabel. Java støtter lat evaluering for følgende spesifikke syntaks:
- Den boolske
&&
og||
operatører, som ikke vil evaluere deres høyre operand når den venstre operanden er falsk (&&
) eller sant (||
). - De
?:
operatør, som evaluerer et boolsk uttrykk og deretter bare evaluerer ett av to alternative uttrykk (av kompatibel type) basert på det boolske uttrykkets sanne / falske verdi.
Funksjonell programmering oppmuntrer til uttrykksorientert programmering, så du vil unngå å bruke utsagn så mye som mulig. Anta for eksempel at du vil erstatte Java hvis
-ellers
uttalelse med en ifThenElse ()
metode. Listing 3 viser et første forsøk.
Oppføring 3. Et eksempel på ivrig evaluering i Java (EagerEval.java)
public class EagerEval {public static void main (String [] args) {System.out.printf ("% d% n", ifThenElse (true, square (4), cube (4))); System.out.printf ("% d% n", ifThenElse (false, kvadrat (4), kube (4))); } statisk int-kube (int x) {System.out.println ("i kube"); returnere x * x * x; } statisk int ifThenElse (boolsk predikat, int onTrue, int onFalse) {retur (predikat)? onTrue: onFalse; } statisk int kvadrat (int x) {System.out.println ("i kvadrat"); return x * x; }}
Oppføring 3 definerer en ifThenElse ()
metoden som tar et boolsk predikat og et par heltall, som returnerer sant
heltall når predikatet er ekte og onFalse
heltall ellers.
Oppføring 3 definerer også kube ()
og torget()
metoder. Disse metodene kuber og kvadraterer et helt tall, og returnerer resultatet.
De hoved()
metoden påkaller ifThenElse (true, square (4), cube (4))
, som bare skal påberope seg firkantet (4)
, etterfulgt av ifThenElse (false, kvadrat (4), kube (4))
, som bare skal påberope seg kube (4)
.
Sammendrag oppføring 3 som følger:
javac EagerEval.java
Kjør den resulterende applikasjonen som følger:
java EagerEval
Du bør følge følgende utdata:
i kvadrat i kube 16 i kvadrat i kube 64
Resultatet viser at hver ifThenElse ()
kaller resultater i at begge metodene kjøres, uavhengig av det boolske uttrykket. Vi kan ikke utnytte ?:
operatørens latskap fordi Java ivrig evaluerer metodens argumenter.
Selv om det ikke er noen måte å unngå ivrig evaluering av metodeargumenter, kan vi fortsatt dra nytte av det ?:
's lat evaluering for å sikre at bare torget()
eller kube ()
er kalt. Oppføring 4 viser hvordan.
Oppføring 4. Et eksempel på lat evaluering i Java (LazyEval.java)
grensesnitt Funksjon {R gjelder (T t); } offentlig klasse LazyEval {public static void main (String [] args) {Function square = new Function () {{System.out.println ("SQUARE"); } @ Override public Integer apply (Integer t) {System.out.println ("in square"); returnere t * t; }}; Funksjonskube = ny funksjon () {{System.out.println ("CUBE"); } @ Override public Integer apply (Integer t) {System.out.println ("in cube"); returnere t * t * t; }}; System.out.printf ("% d% n", ifThenElse (true, square, cube, 4)); System.out.printf ("% d% n", ifThenElse (false, kvadrat, kube, 4)); } statisk R ifThenElse (boolsk predikat, Funksjon onTrue, Funksjon onFalse, T t) {retur (predikat? onTrue.apply (t): onFalse.apply (t)); }}
Oppføring 4 svinger ifThenElse ()
inn i en høyere ordensfunksjon ved å erklære denne metoden for å motta et par Funksjon
argumenter. Selv om disse argumentene blir ivrig evaluert når de sendes til ifThenElse ()
, den ?:
fører bare til at en av disse funksjonene utføres (via søke om()
). Du kan se både ivrig og lat evaluering på jobben når du kompilerer og kjører applikasjonen.
Sett opp liste 4 som følger:
javac LazyEval.java
Kjør den resulterende applikasjonen som følger:
java LazyEval
Du bør følge følgende utdata:
SQUARE CUBE i firkant 16 i terning 64
En lat iterator og mer
Neal Fords "Laziness, Part 1: Exploring lat evaluering in Java" gir mer innsikt i lat evaluering. Forfatteren presenterer en Java-basert lat iterator sammen med et par lat-orienterte Java-rammer.
Stengninger i Java
En anonym indre klasseinstans er assosiert med en nedleggelse. Variabler for ytre omfang må deklareres endelig
eller (starter i Java 8) effektivt endelig (som betyr umodifisert etter initialisering) for å være tilgjengelig. Vurder oppføring 5.
Oppføring 5. Et eksempel på nedleggelser i Java (PartialAdd.java)
grensesnitt Funksjon {R gjelder (T t); } offentlig klasse PartialAdd {Function add (final int x) {Function partialAdd = new Function () {@Override public Integer apply (Integer y) {return y + x; }}; returner delvisLegg til; } public static void main (String [] args) {PartialAdd pa = new PartialAdd (); Funksjon add10 = pa.add (10); Funksjon add20 = pa.add (20); System.out.println (add10.apply (5)); System.out.println (add20.apply (5)); }}
Oppføring 5 er Java-ekvivalenten til nedleggelsen jeg tidligere presenterte i JavaScript (se del 1, oppføring 8). Denne koden erklærer en legge til()
høyere ordensfunksjon som returnerer en funksjon for å utføre delvis anvendelse av legge til()
funksjon. De søke om()
metoden får tilgang til variabel x
i det ytre omfanget av legge til()
, som må erklæres endelig
før Java 8. Koden oppfører seg omtrent som den JavaScript-ekvivalenten.
Sammensett oppføring 5 som følger:
javac PartialAdd.java
Kjør den resulterende applikasjonen som følger:
java PartialAdd
Du bør følge følgende utdata:
15 25
Currying på Java
Du har kanskje lagt merke til at Delvis legge til
i Listing 5 viser mer enn bare nedleggelser. Det demonstrerer også karri, som er en måte å oversette en flerargumentfunks evaluering til evaluering av en ekvivalent sekvens av enkeltargumentfunksjoner. Både pa.add (10)
og pa.add (20)
i Listing 5 returnerer en lukking som registrerer en operand (10
eller 20
henholdsvis) og en funksjon som utfører tillegg - den andre operanden (5
) sendes via add10.apply (5)
eller add20.apply (5)
.
Currying lar oss evaluere funksjonsargumenter en om gangen, og produsere en ny funksjon med ett argument mindre på hvert trinn. For eksempel i Delvis legge til
applikasjon, currying vi følgende funksjon:
f (x, y) = x + y
Vi kunne bruke begge argumentene samtidig, og gi følgende:
f (10, 5) = 10 + 5
Imidlertid, med karri, bruker vi bare det første argumentet, og gir dette:
f (10, y) = g (y) = 10 + y
Vi har nå en enkelt funksjon, g
, det tar bare ett eneste argument. Dette er funksjonen som vil bli evaluert når vi kaller søke om()
metode.
Delvis påføring, ikke delvis tilsetning
Navnet Delvis legge til
står for delvis søknad av legge til()
funksjon. Det står ikke for delvis tillegg. Currying handler om å utføre en delvis anvendelse av en funksjon. Det handler ikke om å utføre delvise beregninger.
Du kan bli forvirret av min bruk av uttrykket "delvis anvendelse", spesielt fordi jeg i del 1 uttalte at karri ikke er det samme som delvis søknad, som er prosessen med å fikse en rekke argumenter til en funksjon, og produsere en annen funksjon med mindre arity. Med delvis applikasjon kan du produsere funksjoner med mer enn ett argument, men med currying må hver funksjon ha nøyaktig ett argument.
Oppføring 5 presenterer et lite eksempel på Java-basert karri før Java 8. Vurder nå CurriedCalc
søknad i oppføring 6.
Oppføring 6. Currying i Java-kode (CurriedCalc.java)
grensesnitt Funksjon {R gjelder (T t); } public class CurriedCalc {public static void main (String [] args) {System.out.println (calc (1) .apply (2) .apply (3) .apply (4)); } statisk funksjon> calc (siste Heltall a) {returnere ny Funksjon> () {@Override public Function gjelder (siste Heltall b) {returnere ny funksjon() {@ Overstyr offentlig funksjon gjelder (endelig heltal c) {returner ny funksjon () {@ overstyr offentlig heltall gjelder (heltall d) {retur (a + b) * (c + d); }}; }}; }}; }}
Oppføring 6 bruker karri for å evaluere funksjonen f (a, b, c, d) = (a + b) * (c + d)
. Gitt uttrykk calc (1) .apply (2) .apply (3) .apply (4)
, blir denne funksjonen curried som følger:
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)
Kompilere oppføring 6:
javac CurriedCalc.java
Kjør den resulterende applikasjonen:
java CurriedCalc
Du bør følge følgende utdata:
21
Fordi curry handler om å utføre en delvis anvendelse av en funksjon, spiller det ingen rolle i hvilken rekkefølge argumentene blir brukt. For eksempel i stedet for å passere en
til calc ()
og d
til de mest nestede søke om()
metode (som utfører beregningen), kan vi reversere disse parameternavnene. Dette vil resultere i d c b a
i stedet for a b c d
, men det ville likevel oppnå det samme resultatet av 21
. (Kildekoden for denne opplæringen inkluderer den alternative versjonen av CurriedCalc
.)
Funksjonell programmering i Java 8
Funksjonell programmering før Java 8 er ikke pen. For mye kode kreves for å opprette, overføre en funksjon til og / eller returnere en funksjon fra en førsteklasses funksjon. Tidligere versjoner av Java mangler også forhåndsdefinerte funksjonelle grensesnitt og førsteklasses funksjoner som filter og kart.
Java 8 reduserer detaljnivået i stor grad ved å introdusere lambdas og metodereferanser til Java-språket. Det tilbyr også forhåndsdefinerte funksjonelle grensesnitt, og det gjør filter, kart, redusering og andre gjenbrukbare førsteklasses funksjoner tilgjengelige via Streams API.
Vi vil se på disse forbedringene sammen i de neste avsnittene.
Skriver lambdas i Java-kode
EN lambda er et uttrykk som beskriver en funksjon ved å betegne en implementering av et funksjonelt grensesnitt. Her er et eksempel:
() -> System.out.println ("min første lambda")
Fra venstre til høyre, ()
identifiserer lambdas formelle parameterliste (det er ingen parametere), ->
betyr et lambdauttrykk, og System.out.println ("min første lambda")
er lambdas kropp (koden som skal utføres).
En lambda har en type, som er et hvilket som helst funksjonelt grensesnitt som lambda er en implementering for. En slik type er java.lang.Runnable
, fordi Kjørbar
s ugyldig kjøre ()
metoden har også en tom formell parameterliste:
Runnable r = () -> System.out.println ("min første lambda");
Du kan passere lambda hvor som helst Kjørbar
argument er nødvendig; for eksempel Tråd (Runnable r)
konstruktør. Forutsatt at forrige oppgave har skjedd, kan du bestå r
til denne konstruktøren, som følger:
ny tråd (r);
Alternativt kan du sende lambda direkte til konstruktøren:
ny tråd (() -> System.out.println ("min første lambda"));
Dette er definitivt mer kompakt enn pre-Java 8-versjonen:
new Thread (new Runnable () {@Override public void run () {System.out.println ("min første lambda");}});
Et lambda-basert filfilter
Min tidligere demonstrasjon av høyere ordensfunksjoner presenterte et filfilter basert på en anonym indre klasse. Her er det lambda-baserte ekvivalenten:
File [] txtFiles = new File ("."). ListFiles (p -> p.getAbsolutePath (). EndsWith ("txt"));
Returner uttalelser i lambdauttrykk
I del 1 nevnte jeg at funksjonelle programmeringsspråk fungerer med uttrykk i motsetning til utsagn. Før Java 8 kunne du i stor grad eliminere utsagn i funksjonell programmering, men du kunne ikke eliminere komme tilbake
uttalelse.
Ovennevnte kodefragment viser at en lambda ikke krever en komme tilbake
uttalelse for å returnere en verdi (en boolsk sann / falsk verdi, i dette tilfellet): du spesifiserer bare uttrykket uten komme tilbake
[og legg til] et semikolon. For lamdas med flere utsagn trenger du imidlertid fortsatt komme tilbake
uttalelse. I disse tilfellene må du plassere lambdas kropp mellom seler som følger (ikke glem semikolonet for å avslutte uttalelsen):
File [] txtFiles = new File ("."). ListFiles (p -> {return p.getAbsolutePath (). EndsWith ("txt");});
Lambdas med funksjonelle grensesnitt
Jeg har to eksempler til for å illustrere kortfattetheten til lambdas. La oss først se på hoved()
metoden fra Sortere
applikasjonen vist i liste 2:
public static void main (String [] args) {String [] innerplanets = {"Mercury", "Venus", "Earth", "Mars"}; dump (indre planeter); sorter (indre planeter, (e1, e2) -> e1.compareTo (e2)); dump (indre planeter); sorter (indre planeter, (e1, e2) -> e2.compareTo (e1)); dump (indre planeter); }
Vi kan også oppdatere calc ()
metoden fra CurriedCalc
applikasjonen vist i Listing 6:
statisk funksjon> calc (Heltall a) {retur b -> c -> d -> (a + b) * (c + d); }
Kjørbar
, FileFilter
, og Komparator
er eksempler på funksjonelle grensesnitt, som beskriver funksjoner. Java 8 formaliserte dette konseptet ved å kreve at et funksjonelt grensesnitt skal kommenteres med java.lang.FunctionalInterface
merknadstype, som i @FunctionalInterface
. Et grensesnitt som er merket med denne typen, må erklære nøyaktig én abstrakt metode.
Du kan bruke Java's forhåndsdefinerte funksjonelle grensesnitt (diskutert senere), eller du kan enkelt spesifisere dine egne, som følger:
@FunctionalInterface interface Funksjon {R gjelder (T t); }
Du kan da bruke dette funksjonelle grensesnittet som vist her:
public static void main (String [] args) {System.out.println (getValue (t -> (int) (Math.random () * t), 10)); System.out.println (getValue (x -> x * x, 20)); } statisk Heltall getValue (Funksjon f, int x) {retur f.apply (x); }
Ny hos lambdas?
Hvis du ikke er kjent med lambdas, trenger du kanskje mer bakgrunn for å forstå disse eksemplene. I så fall, sjekk ut den videre introduksjonen min til lambdas og funksjonelle grensesnitt i "Kom i gang med lambdauttrykk i Java." Du finner også mange nyttige blogginnlegg om dette emnet. Et eksempel er "Funksjonell programmering med Java 8-funksjoner", der forfatter Edwin Dalorzo viser hvordan man bruker lambdauttrykk og anonyme funksjoner i Java 8.
Arkitektur av en lambda
Hver lambda er til slutt en forekomst av noen klasse som genereres bak kulissene. Utforsk følgende ressurser for å lære mer om lambda-arkitektur:
- "Hvordan lambdas og anonyme indre klasser fungerer" (Martin Farrell, DZone)
- "Lambdas in Java: A peek under the hood" (Brian Goetz, GOTO)
- "Hvorfor blir Java 8 lambdas påkalt ved hjelp av invokedynamic?" (Stack Overflow)
Jeg tror du vil finne Java Language Architect Brian Goetzs videopresentasjon av hva som skjer under panseret med lambdas spesielt fascinerende.
Metodehenvisninger i Java
Noen lambdas påkaller bare en eksisterende metode. For eksempel påkaller følgende lambda System.out
s ugyldig utskrift (er)
metode på lambdas eneste argument:
(Streng s) -> System.out.println (s)
Lambda presenterer (Streng s)
som sin formelle parameterliste og en kodetype hvis System.out.println (s)
uttrykk utskrifter s
verdi til standard utgangsstrøm.
For å lagre tastetrykk kan du erstatte lambda med en metodehenvisning, som er en kompakt referanse til en eksisterende metode. For eksempel kan du erstatte det forrige kodefragmentet med følgende:
System.out :: println
Her, ::
betyr det System.out
s ugyldig utskrift (streng s)
metode refereres til. Metodereferansen resulterer i mye kortere kode enn vi oppnådde med forrige lambda.
En metodehenvisning for Sort
Jeg har tidligere vist en lambda-versjon av Sortere
søknad fra oppføring 2. Her er den samme koden skrevet med en metodehenvisning i stedet:
public static void main (String [] args) {String [] innerplanets = {"Mercury", "Venus", "Earth", "Mars"}; dump (indre planeter); sorter (indre planeter, String :: CompareTo); dump (indre planeter); sorter (innerplanets, Comparator.comparing (String :: toString) .reversed ()); dump (indre planeter); }
De String :: Sammenlign med
metode referanse versjon er kortere enn lambda versjonen av (e1, e2) -> e1.compareTo (e2)
. Vær imidlertid oppmerksom på at det kreves et lengre uttrykk for å lage en tilsvarende omvendt ordensortering, som også inkluderer en metodereferanse: String :: toString
. I stedet for å spesifisere String :: toString
Jeg kunne ha spesifisert tilsvarende s -> s.toString ()
lambda.
Mer om metodereferanser
Metodehenvisninger har mye mer enn jeg kunne dekke i et begrenset rom. For å lære mer, sjekk ut introduksjonen til skrivemetoderhenvisninger for statiske metoder, ikke-statiske metoder og konstruktører i "Kom i gang med metodereferanser i Java."
Forhåndsdefinerte funksjonelle grensesnitt
Java 8 introduserte forhåndsdefinerte funksjonelle grensesnitt (java.util.funksjon
) slik at utviklere ikke har opprettet våre egne funksjonelle grensesnitt for vanlige oppgaver. Her er noen eksempler:
- De
Forbruker
funksjonelt grensesnitt representerer en operasjon som godtar et enkelt inngangsargument og ikke gir noe resultat. Det erugyldig godta (T t)
metoden utfører denne operasjonen på argumentt
. - De
Funksjon
funksjonelt grensesnitt representerer en funksjon som godtar ett argument og returnerer et resultat. Det erR gjelder (T t)
metoden bruker denne funksjonen til argumentt
og returnerer resultatet. - De
Predikere
funksjonelt grensesnitt representerer en predikat (Funksjon som er verdsatt av Boolean) av ett argument. Det erboolsk test (T t)
metoden evaluerer dette predikatet på argumentett
og returnerer sant eller usant. - De
Leverandør
funksjonelt grensesnitt representerer en leverandør av resultater. Det erT får ()
metoden mottar ingen argument (er), men returnerer et resultat.
De DaysInMonth
søknad i Listing 1 avslørte en komplett Funksjon
grensesnitt. Fra og med Java 8, kan du fjerne dette grensesnittet og importere det identiske forhåndsdefinerte Funksjon
grensesnitt.
Mer om forhåndsdefinerte funksjonelle grensesnitt
"Kom i gang med lambdauttrykk i Java" gir eksempler på Forbruker
og Predikere
funksjonelle grensesnitt. Sjekk ut blogginnlegget "Java 8 - Lazy argument evaluering" for å oppdage en interessant bruk for Leverandør
.
I tillegg, selv om de forhåndsdefinerte funksjonelle grensesnittene er nyttige, presenterer de også noen problemer. Blogger Pierre-Yves Saumont forklarer hvorfor.
Funksjonelle APIer: Strømmer
Java 8 introduserte Streams API for å lette sekvensiell og parallell behandling av dataelementer. Denne API-en er basert på bekker, hvor en strøm er en sekvens av elementer som stammer fra en kilde og støtter sekvensielle og parallelle aggregatoperasjoner. EN kilde lagrer elementer (for eksempel en samling) eller genererer elementer (for eksempel en tilfeldig tallgenerator). An samlet er et resultat beregnet fra flere inngangsverdier.
En strøm støtter mellom- og terminaloperasjoner. An mellomdrift returnerer en ny strøm, mens a terminaldrift forbruker strømmen. Operasjoner er koblet til en rørledning (via metodekjetting). Rørledningen starter med en kilde, som blir fulgt av null eller flere mellomliggende operasjoner, og slutter med en terminaloperasjon.
Strømmer er et eksempel på en funksjonell API. Den tilbyr filter, kart, redusering og andre gjenbrukbare førsteklasses funksjoner. Jeg demonstrerte kort denne APIen i Ansatte
applikasjonen vist i del 1, oppføring 1. Oppføring 7 gir et annet eksempel.
Oppføring 7. Funksjonell programmering med Streams (StreamFP.java)
importere java.util.Random; importere 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 (); String [] cities = {"New York", "London", "Paris", "Berlin", "BrasÌlia", "Tokyo", "Beijing", "Jerusalem", "Cairo", "Riyadh", "Moscow" }; IntStream.range (0, 11) .mapToObj (i -> byer [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 :: sum)); }}
De hoved()
metoden oppretter først en strøm av pseudorandom heltall som starter ved 0 og slutter ved 10. Strømmen er begrenset til nøyaktig 10 heltall. De filter()
førsteklasses funksjon mottar en lambda som sitt predikatargument. Predikatet fjerner odde heltall fra strømmen. Til slutt, for hver()
førsteklasses funksjon skriver ut hvert jevne heltall til standardutgangen via System.out :: println
metodehenvisning.
De hoved()
metoden oppretter deretter et heltallstrøm som produserer et sekvensielt utvalg av heltall som starter ved 0 og slutter ved 10. The mapToObj ()
førsteklasses funksjon mottar en lambda som tilordner et heltall til ekvivalent streng ved heltallindeksen i byer
array. Bynavnet blir deretter sendt til standardutgangen via for hver()
førsteklasses funksjon og dens System.out :: println
metodehenvisning.
Til slutt, hoved()
demonstrerer redusere()
førsteklasses funksjon. En heltallstrøm som produserer det samme spekteret av heltall som i forrige eksempel, reduseres til en sum av deres verdier, som deretter sendes ut.
Identifisere mellom- og terminaloperasjoner
Hver av grense()
, filter()
, område()
, og mapToObj ()
er mellomliggende operasjoner, mens for hver()
og redusere()
er terminaloperasjoner.
Sammendrag oppføring 7 som følger:
javac StreamFP.java
Kjør den resulterende applikasjonen som følger:
java StreamFP
Jeg observerte følgende utdata fra ett løp:
0 2 10 6 0 8 10 New York London Paris Berlin BrasÌlia Tokyo Beijing Jerusalem Kairo Riyadh Moskva 45 45
Du har kanskje forventet 10 i stedet for 7 pseudorandom til og med heltall (alt fra 0 til 10, takket være rekkevidde (0, 11)
) for å vises i begynnelsen av utgangen. Tross alt, grense (10)
ser ut til å indikere at 10 heltall vil bli sendt ut. Dette er imidlertid ikke tilfelle. Selv om grense (10)
resulterer i en strøm på nøyaktig 10 heltall, filter (x -> x% 2 == 0)
samtale resulterer i at odde heltall blir fjernet fra strømmen.
Mer om Streams
Hvis du ikke er kjent med Streams, kan du sjekke ut veiledningen min om å introdusere Java SE 8s nye Streams API for mer om dette funksjonelle APIet.
For å konkludere
Mange Java-utviklere vil ikke forfølge ren funksjonell programmering på et språk som Haskell fordi det skiller seg så sterkt fra det kjente tvingende, objektorienterte paradigmet. Java 8s funksjonelle programmeringsfunksjoner er designet for å bygge bro over gapet, slik at Java-utviklere kan skrive kode som er lettere å forstå, vedlikeholde og teste. Funksjonell kode er også mer gjenbrukbar og mer egnet for parallell behandling i Java. Med alle disse insentivene er det egentlig ingen grunn til ikke å innlemme Java's funksjonelle programmeringsalternativer i Java-koden din.
Skriv en funksjonell Bubble Sort-applikasjon
Funksjonell tenkning er et begrep som er laget av Neal Ford, som refererer til det kognitive skiftet fra det objektorienterte paradigmet til det funksjonelle programmeringsparadigmet. Som du har sett i denne opplæringen, er det mulig å lære mye om funksjonell programmering ved å omskrive objektorientert kode ved hjelp av funksjonelle teknikker.
Avgrens det du har lært så langt ved å gå tilbake til Sorteringsapplikasjonen fra oppføring 2. I dette raske tipset viser jeg deg hvordan du gjør det skriv en rent funksjonell Bubble Sort, først ved å bruke pre-Java 8-teknikker, og deretter bruke Java 8s funksjonelle funksjoner.
Denne historien, "Funksjonell programmering for Java-utviklere, del 2" ble opprinnelig utgitt av JavaWorld.