Programmering

Funktionel programmering til Java-udviklere, del 2

Velkommen tilbage til denne todelt tutorial, der introducerer funktionel programmering i en Java-kontekst. I funktionel programmering til Java-udviklere, del 1, brugte jeg JavaScript-eksempler til at komme i gang med fem funktionelle programmeringsteknikker: rene funktioner, højere ordensfunktioner, doven evaluering, lukning og currying. Præsentation af disse eksempler i JavaScript tillod os at fokusere på teknikkerne i en enklere syntaks uden at komme ind på Java's mere komplekse funktionelle programmeringsfunktioner.

I del 2 gennemgår vi disse teknikker ved hjælp af Java-kode, der foruddaterer Java 8. Som du vil se, er denne kode funktionel, men den er ikke let at skrive eller læse. Du vil også blive introduceret til de nye funktionelle programmeringsfunktioner, der var fuldt integreret i Java-sproget i Java 8; nemlig lambdas, metodehenvisninger, funktionelle grænseflader og Streams API.

I hele denne tutorial gennemgår vi eksempler fra del 1 for at se, hvordan JavaScript- og Java-eksemplerne sammenlignes. Du vil også se, hvad der sker, når jeg opdaterer nogle af de præ-Java 8 eksempler med funktionelle sprogfunktioner som lambdas og metodehenvisninger. Endelig inkluderer denne vejledning en praktisk øvelse designet til at hjælpe dig øve funktionel tænkning, som du gør ved at omdanne et stykke objektorienteret Java-kode til dets funktionelle ækvivalent.

download Hent koden Download kildekoden for eksempel applikationer i denne vejledning. Oprettet af Jeff Friesen til JavaWorld.

Funktionel programmering med Java

Mange udviklere er ikke klar over det, men det var muligt at skrive funktionelle programmer i Java før Java 8. For at få et godt afrundet billede af funktionel programmering i Java, lad os hurtigt gennemgå funktionelle programmeringsfunktioner, der går forud for Java 8. Når du Hvis du har dem nede, vil du sandsynligvis have større forståelse for, hvordan nye funktioner introduceret i Java 8 (som lambdas og funktionelle grænseflader) har forenklet Java's tilgang til funktionel programmering.

Grænser for Java's understøttelse af funktionel programmering

Selv med funktionelle programmeringsforbedringer i Java 8 forbliver Java et bydende nødvendigt, objektorienteret programmeringssprog. Det mangler rækketyper og andre funktioner, der ville gøre det mere funktionelt. Java er også hoblet af nominativ typing, hvilket er betingelsen om, at hver type skal have et navn. På trods af disse begrænsninger har udviklere, der omfavner Java's funktionelle funktioner, stadig fordel af at kunne skrive mere kortfattet, genanvendelig og læsbar kode.

Funktionel programmering før Java 8

Anonyme indre klasser sammen med grænseflader og lukninger er tre ældre funktioner, der understøtter funktionel programmering i ældre versioner af Java:

  • Anonyme indre klasser lad dig videregive funktionalitet (beskrevet af grænseflader) til metoder.
  • Funktionelle grænseflader er grænseflader, der beskriver en funktion.
  • Lukninger lad dig få adgang til variabler i deres ydre rækkevidde.

I de efterfølgende afsnit gennemgår vi de fem teknikker, der blev introduceret i del 1, men ved hjælp af Java-syntaks. Du kan se, hvordan hver af disse funktionelle teknikker var mulige før Java 8.

Skrivning af rene funktioner i Java

Liste 1 viser kildekoden til et eksempel på applikation, DaysInMonth, der er skrevet ved hjælp af en anonym indre klasse og en funktionel grænseflade. Denne applikation demonstrerer, hvordan man skriver en ren funktion, som man kunne opnå i Java længe før Java 8.

Liste 1. En ren funktion i Java (DaysInMonth.java)

interface Funktion {R gælder (Tt); } 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)); }}

Det generiske Fungere interface i liste 1 beskriver en funktion med en enkelt parameter af typen T og en returtype af typen R. Det Fungere interface erklærer en R gælder (T t) metode, der anvender denne funktion til det givne argument.

Det hoved () Metoden instantierer en anonym indre klasse, der implementerer Fungere interface. Det ansøge() metode udpakker måned og bruger det til at indeksere en matrix af dage-i-måned-heltal. Helt tal ved dette indeks returneres. (Jeg ignorerer skudår for enkelhedens skyld.)

hoved () næste udfører denne funktion to gange ved at påberåbe sig ansøge() at returnere dagtællingerne for månederne april og august. Disse optællinger udskrives efterfølgende.

Vi har formået at oprette en funktion og en ren funktion på det! Husk at en ren funktion afhænger kun af dets argumenter og ingen ekstern tilstand. Der er ingen bivirkninger.

Kompilér liste 1 som følger:

javac DaysInMonth.java

Kør den resulterende applikation som følger:

java DaysInMonth

Du skal overholde følgende output:

April: 30. august: 31.

Skrivning af højere ordensfunktioner i Java

Dernæst ser vi på højere ordensfunktioner, også kendt som førsteklasses funktioner. Husk at en højere ordens funktion modtager funktionsargumenter og / eller returnerer et funktionsresultat. Java forbinder en funktion med en metode, der er defineret i en anonym indre klasse. En forekomst af denne klasse sendes til eller returneres fra en anden Java-metode, der fungerer som højere ordensfunktion. Følgende filorienterede kodefragment demonstrerer overførsel af en funktion til en højere ordensfunktion:

File [] txtFiles = new File ("."). ListFiles (new FileFilter () {@Override public boolean accept (File pathname) {return pathname.getAbsolutePath (). EndsWith ("txt");}});

Dette kodefragment passerer en funktion baseret på java.io.FileFilter funktionel grænseflade til java.io-fil klassens File [] listFiles (FileFilter filter) metode og fortæller, at den kun skal returnere disse filer med txt udvidelser.

Liste 2 viser en anden måde at arbejde med højere ordensfunktioner på Java. I dette tilfælde overfører koden en komparatorfunktion til en sortere() højere ordens funktion for en stigende rækkefølge og en anden komparatorfunktion til sortere() for en faldende ordensortering.

Liste 2. En højere ordensfunktion i Java (Sort.java)

import java.util.Comparator; public class Sorter {public static void main (String [] args) {String [] innerplanets = {"Mercury", "Venus", "Earth", "Mars"}; dump (indre planeter); sorter (indre planeter, ny komparator () {@ Override offentlig int sammenligning (String e1, String e2) {return e1.compareTo (e2);}}); dump (indre planeter); sorter (indre planeter, ny komparator () {@ Override offentlig int sammenligning (String e1, String e2) {return e2.compareTo (e1);}}); dump (indre planeter); } statisk tomrumsdump (T [] array) {for (T element: array) System.out.println (element); System.out.println (); } statisk tomrums sortering (T [] array, Comparator cmp) {for (int pass = 0; pass  passere; i--) hvis (cmp.compare (array [i], array [pass]) <0) swap (array, i, pass); } statisk tomrumsudskiftning (T [] array, int i, int j) {T temp = array [i]; matrix [i] = matrix [j]; matrix [j] = temp; }}

Liste 2 importerer java.util.Comparator funktionel grænseflade, der beskriver en funktion, der kan udføre en sammenligning på to objekter af vilkårlig, men identisk type.

To vigtige dele af denne kode er sortere() metode (som implementerer Bubble Sort algoritmen) og sortere() påkaldelser i hoved () metode. Selvom sortere() er langt fra at være funktionel, viser den en højere ordensfunktion, der modtager en funktion - komparatoren - som et argument. Den udfører denne funktion ved at påberåbe sig dens sammenligne() metode. To forekomster af denne funktion sendes i to sortere() ringer ind hoved ().

Kompilér liste 2 som følger:

javac Sort.java

Kør den resulterende applikation som følger:

java Sort

Du skal overholde følgende output:

Mercury Venus Earth Mars Earth Mercury Venus Venus Mercury Mars Earth

Lazy evaluering i Java

Lazy evaluering er en anden funktionel programmeringsteknik, der ikke er ny for Java 8. Denne teknik forsinker evalueringen af ​​et udtryk, indtil dets værdi er nødvendig. I de fleste tilfælde evaluerer Java ivrigt et udtryk, der er bundet til en variabel. Java understøtter doven evaluering af følgende specifikke syntaks:

  • Den boolske && og || operatører, som ikke evaluerer deres højre operand, når den venstre operand er falsk (&&) eller sandt (||).
  • Det ?: operatør, som evaluerer et boolsk udtryk og derefter kun evaluerer et af to alternative udtryk (af kompatibel type) baseret på det boolske udtryks sande / falske værdi.

Funktionel programmering tilskynder udtryk-orienteret programmering, så du vil undgå at bruge udsagn så meget som muligt. Antag for eksempel, at du vil erstatte Java'er hvis-andet erklæring med en ifThenElse () metode. Liste 3 viser et første forsøg.

Liste 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 (falsk, firkantet (4), terning (4))); } statisk int-terning (int x) {System.out.println ("i terning"); returnere x * x * x; } statisk int ifThenElse (boolsk predikat, int onTrue, int onFalse) {return (predikat)? onTrue: onFalse; } statisk int kvadrat (int x) {System.out.println ("i kvadrat"); returnere x * x; }}

Liste 3 definerer en ifThenElse () metode, der tager et boolsk prædikat og et par heltal, der returnerer onTrue heltal, når prædikatet er rigtigt og onFalse heltal ellers.

Liste 3 definerer også terning () og firkant() metoder. Disse metoder kuberer og kvadrerer et heltal og returnerer resultatet.

Det hoved () metode påberåber sig ifThenElse (sand, firkantet (4), terning (4)), som kun skal påberåbes firkantet (4), efterfulgt af ifThenElse (falsk, firkantet (4), terning (4)), som kun skal påberåbes terning (4).

Kompilér liste 3 som følger:

javac EagerEval.java

Kør den resulterende applikation som følger:

java EagerEval

Du skal overholde følgende output:

i firkant i terning 16 i firkant i terning 64

Outputtet viser, at hver ifThenElse () kalder resultater i begge metoder, der udføres, uanset det boolske udtryk. Vi kan ikke udnytte ?: operatørs dovenskab, fordi Java ivrigt evaluerer metodens argumenter.

Selvom der ikke er nogen måde at undgå ivrig evaluering af metodeargumenter, kan vi stadig drage fordel af ?:'s dovne evaluering for kun at sikre, at firkant() eller terning () Hedder. Liste 4 viser hvordan.

Fortegnelse 4. Et eksempel på doven evaluering i Java (LazyEval.java)

interface Funktion {R gælder (Tt); } 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; }}; Funktionskube = ny funktion () {{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 (sand, firkantet, terning, 4)); System.out.printf ("% d% n", ifThenElse (falsk, firkantet, terning, 4)); } statisk R ifThenElse (boolsk predikat, funktion onTrue, funktion onfalsk, T t) {return (predikat? onTrue.apply (t): onFalse.apply (t)); }}

Notering 4 drejninger ifThenElse () ind i en højere ordensfunktion ved at erklære denne metode for at modtage et par Fungere argumenter. Selvom disse argumenter evigt evalueres, når de overføres til ifThenElse (), det ?: operatør får kun en af ​​disse funktioner til at udføre (via ansøge()). Du kan se både ivrig og doven evaluering på arbejdspladsen, når du kompilerer og kører applikationen.

Kompilér liste 4 som følger:

javac LazyEval.java

Kør den resulterende applikation som følger:

java LazyEval

Du skal overholde følgende output:

SQUARE CUBE i firkant 16 i terning 64

En doven iterator og mere

Neal Fords "Laziness, Part 1: Exploring doven evaluering in Java" giver mere indsigt i doven evaluering. Forfatteren præsenterer en Java-baseret doven iterator sammen med et par dov-orienterede Java-rammer.

Lukninger i Java

En anonym indre klasseinstans er forbundet med en lukning. Variabler for ydre omfang skal deklareres endelig eller (startende i Java 8) effektivt endelig (betyder uændret efter initialisering) for at være tilgængelig. Overvej at liste 5.

Liste 5. Et eksempel på lukninger i Java (PartialAdd.java)

interface Funktion {R gælder (Tt); } offentlig klasse PartialAdd {Funktion tilføj (endelig int x) {Funktion partialAdd = ny funktion () {@ Overstyr offentlig Heltal anvend (Heltal y) {return y + x; }}; returnere partialTilføj; } offentlig statisk ugyldig hoved (String [] args) {PartialAdd pa = new PartialAdd (); Funktion add10 = pa.add (10); Funktion add20 = pa.add (20); System.out.println (add10.apply (5)); System.out.println (add20.apply (5)); }}

Listing 5 er Java-ækvivalenten for lukningen, som jeg tidligere præsenterede i JavaScript (se del 1, liste 8). Denne kode erklærer en tilføje() højere ordensfunktion, der returnerer en funktion til delvis udførelse af tilføje() fungere. Det ansøge() metode adgang variabel x i det ydre omfang af tilføje(), som skal erklæres endelig forud for Java 8. Koden opfører sig stort set den samme som JavaScript-ækvivalenten.

Kompilér liste 5 som følger:

javac PartialAdd.java

Kør den resulterende applikation som følger:

java PartialAdd

Du skal overholde følgende output:

15 25

Curry i Java

Du har måske bemærket, at DelvisTilføj i Listing 5 viser mere end bare lukninger. Det demonstrerer også karry, som er en måde at oversætte en flerargumentfunktions evaluering til evaluering af en ækvivalent sekvens af enkeltargumentfunktioner. Begge pa.add (10) og pa.add (20) i Listing 5 returnerer en lukning, der registrerer en operand (10 eller 20henholdsvis) og en funktion, der udfører tilføjelsen - den anden operand (5) sendes via tilføj10.apply (5) eller add20.apply (5).

Currying giver os mulighed for at evaluere funktionsargumenter en ad gangen og producere en ny funktion med et argument mindre på hvert trin. For eksempel i DelvisTilføj applikation, vi curry følgende funktion:

f (x, y) = x + y

Vi kunne anvende begge argumenter på samme tid og give følgende:

f (10, 5) = 10 + 5

Men med curry anvender vi kun det første argument, hvilket giver dette:

f (10, y) = g (y) = 10 + y

Vi har nu en enkelt funktion, g, der kun tager et enkelt argument. Dette er den funktion, der evalueres, når vi kalder ansøge() metode.

Delvis anvendelse, ikke delvis tilsætning

Navnet DelvisTilføj står for delvis anvendelse af tilføje() fungere. Det står ikke for delvis tilføjelse. Currying handler om at udføre delvis anvendelse af en funktion. Det handler ikke om at udføre delberegninger.

Du kan blive forvirret af min brug af sætningen "delvis anvendelse", især fordi jeg i del 1 sagde, at curry ikke er det samme som delvis anvendelse, som er processen med at rette et antal argumenter til en funktion, der producerer en anden funktion med mindre aritet. Med delvis anvendelse kan du producere funktioner med mere end et argument, men med currying skal hver funktion have nøjagtigt et argument.

Listing 5 præsenterer et lille eksempel på Java-baseret currying før Java 8. Overvej nu CurriedCalc ansøgning i liste 6.

Liste 6. Currying i Java-kode (CurriedCalc.java)

interface Funktion {R gælder (Tt); } public class CurriedCalc {public static void main (String [] args) {System.out.println (calc (1) .apply (2) .apply (3) .apply (4)); } statisk funktion> calc (sidste heltal a) {returner ny funktion> () {@Overstyr offentlig funktion anvend (sidste heltal b) {returner ny funktion() {@Override public Function apply (final Integer c) {return new Function () {@Override public Integer apply (Integer d) {return (a + b) * (c + d); }}; }}; }}; }}

Listing 6 bruger currying til at evaluere funktionen f (a, b, c, d) = (a + b) * (c + d). Givet udtryk calc (1) .apply (2) .apply (3) .apply (4), er denne funktion curried som følger:

  1. f (1, b, c, d) = g (b, c, d) = (1 + b) * (c + d)
  2. g (2, c, d) = h (c, d) = (1 + 2) * (c + d)
  3. h (3, d) = i (d) = (1 + 2) * (3 + d)
  4. i (4) = (1 + 2) * (3 + 4)

Kompilering Listing 6:

javac CurriedCalc.java

Kør den resulterende applikation:

java CurriedCalc

Du skal overholde følgende output:

21

Fordi curry handler om at udføre delvis anvendelse af en funktion, betyder det ikke noget i hvilken rækkefølge argumenterne anvendes. For eksempel i stedet for at passere -en til calc () og d til de mest indlejrede ansøge() metode (som udfører beregningen), kunne vi vende disse parameternavne. Dette ville resultere i d c b a i stedet for a b c d, men det ville stadig opnå det samme resultat af 21. (Kildekoden til denne vejledning inkluderer den alternative version af CurriedCalc.)

Funktionel programmering i Java 8

Funktionel programmering før Java 8 er ikke smuk. Der kræves for meget kode til at oprette, videregive en funktion til og / eller returnere en funktion fra en førsteklasses funktion. Tidligere versioner af Java mangler også foruddefinerede funktionelle grænseflader og førsteklasses funktioner såsom filter og kort.

Java 8 reducerer storhedsgraden i vid udstrækning ved at introducere lambdas og metodehenvisninger til Java-sproget. Det tilbyder også foruddefinerede funktionelle grænseflader, og det gør filter, kortlægning, reduktion og andre genanvendelige førsteklasses funktioner tilgængelige via Streams API.

Vi ser på disse forbedringer sammen i de næste sektioner.

Skrivning af lambdas i Java-kode

EN lambda er et udtryk, der beskriver en funktion ved at betegne en implementering af en funktionel grænseflade. Her er et eksempel:

() -> System.out.println ("min første lambda")

Fra venstre mod højre, () identificerer lambdas formelle parameterliste (der er ingen parametre), -> betyder et lambda-udtryk, og System.out.println ("min første lambda") er lambdas krop (koden, der skal udføres).

En lambda har en type, hvilket er en hvilken som helst funktionel grænseflade, som lambda er en implementering for. En sådan type er java.lang.Køres, fordi Kan køres's ugyldig kørsel () metoden har også en tom formel parameterliste:

Runnable r = () -> System.out.println ("min første lambda");

Du kan passere lambda hvor som helst Kan køres argument er påkrævet; for eksempel Tråd (kørbar r) konstruktør. Forudsat at den tidligere opgave er fundet, kan du bestå r til denne konstruktør 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 bestemt mere kompakt end pre-Java 8-versionen:

ny tråd (ny Runnable () {@ Override public void run () {System.out.println ("min første lambda");}});

Et lambda-baseret filfilter

Min tidligere demonstration af højere ordensfunktioner præsenterede et filfilter baseret på en anonym indre klasse. Her er det lambda-baserede ækvivalent:

File [] txtFiles = new File ("."). ListFiles (p -> p.getAbsolutePath (). EndsWith ("txt"));

Returner udsagn i lambda-udtryk

I del 1 nævnte jeg, at funktionelle programmeringssprog fungerer med udtryk i modsætning til udsagn. Før Java 8 kunne du stort set fjerne udsagn i funktionel programmering, men du kunne ikke fjerne Vend tilbage udmelding.

Ovenstående kodefragment viser, at en lambda ikke kræver en Vend tilbage erklæring for at returnere en værdi (en boolsk sand / falsk værdi, i dette tilfælde): du angiver bare udtrykket uden Vend tilbage [og tilføj] et semikolon. For lamdas med flere udsagn har du dog stadig brug for Vend tilbage udmelding. I disse tilfælde skal du placere lambdas krop mellem seler som følger (glem ikke semikolonet for at afslutte udsagnet):

File [] txtFiles = new File ("."). ListFiles (p -> {return p.getAbsolutePath (). EndsWith ("txt");});

Lambdas med funktionelle grænseflader

Jeg har yderligere to eksempler for at illustrere lambdas kortfattethed. Lad os først besøge hoved () metode fra Sortere applikation 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å opdatere calc () metode fra CurriedCalc applikation vist i liste 6:

statisk funktion> calc (Heltal a) {return b -> c -> d -> (a + b) * (c + d); }

Kan køres, FileFilterog Komparator er eksempler på funktionelle grænseflader, som beskriver funktioner. Java 8 formaliserede dette koncept ved at kræve, at en funktionel grænseflade skal kommenteres med java.lang.FunctionalInterface annotationstype, som i @FunktionelInterface. En grænseflade, der er kommenteret med denne type, skal erklære nøjagtigt en abstrakt metode.

Du kan bruge Java's foruddefinerede funktionelle grænseflader (diskuteret senere), eller du kan nemt angive dine egne som følger:

@FunctionalInterface interface Funktion {R gælder (T t); }

Du kan derefter bruge denne funktionelle grænseflade 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 heltal getValue (funktion f, int x) {return f.apply (x); }

Ny på lambdas?

Hvis du er ny hos lambdas, har du muligvis brug for mere baggrund for at forstå disse eksempler. I så fald skal du tjekke min yderligere introduktion til lambdas og funktionelle grænseflader i "Kom godt i gang med lambda-udtryk i Java." Du finder også adskillige nyttige blogindlæg om dette emne. Et eksempel er "Funktionel programmering med Java 8-funktioner", hvor forfatter Edwin Dalorzo viser, hvordan man bruger lambda-udtryk og anonyme funktioner i Java 8.

Arkitektur af en lambda

Hver lambda er i sidste ende en forekomst af en klasse, der genereres bag kulisserne. Udforsk følgende ressourcer for at lære mere 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 påberåbes Java 8 lambdas ved hjælp af invokedynamic?" (Stack Overflow)

Jeg tror, ​​du finder Java Language Architect Brian Goetzs videopræsentation af, hvad der foregår under emhætten med lambdas, specielt fascinerende.

Metodehenvisninger i Java

Nogle lambdas påberåber sig kun en eksisterende metode. For eksempel påkalder følgende lambda System.out's ugyldige println (er) metode på lambdas enkelte argument:

(Streng (er)) -> System.out.println (s)

Lambda præsenterer (Streng s) som sin formelle parameterliste og et kodeorgan, hvis System.out.println (s) udtryk udskrives s's værdi til standard output stream.

For at gemme tastetryk kan du erstatte lambda med en metodehenvisning, som er en kompakt henvisning til en eksisterende metode. For eksempel kan du erstatte det forrige kodefragment med følgende:

System.out :: println

Her, :: betyder det System.out's ugyldig println (streng s) der refereres til metoden. Metodehenvisningen resulterer i meget kortere kode, end vi opnåede med den forrige lambda.

En metodehenvisning til Sort

Jeg har tidligere vist en lambda-version af Sortere ansøgning fra liste 2. Her er den samme kode 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 :: sammenlignTo); dump (indre planeter); sorter (innerplanets, Comparator.comparing (String :: toString) .reversed ()); dump (indre planeter); }

Det String :: Sammenlign med metoden reference version er kortere end lambda versionen af (e1, e2) -> e1.compareTo (e2). Bemærk dog, at der kræves et længere udtryk for at oprette en tilsvarende omvendt rækkefølge, som også inkluderer en metodereference: String :: toString. I stedet for at specificere String :: toString, Kunne jeg have specificeret det tilsvarende s -> s.toString () lambda.

Mere om metodereferencer

Der er meget mere til metodereferencer, end jeg kunne dække i et begrænset rum. For at lære mere, se min introduktion til skrivemetoderhenvisninger for statiske metoder, ikke-statiske metoder og konstruktører i "Kom godt i gang med metodereferencer i Java."

Foruddefinerede funktionelle grænseflader

Java 8 introducerede foruddefinerede funktionelle grænseflader (java.util.function), så udviklere ikke har oprettet vores egne funktionelle grænseflader til fælles opgaver. Her er et par eksempler:

  • Det Forbruger funktionel grænseflade repræsenterer en operation, der accepterer et enkelt inputargument og ikke returnerer noget resultat. Dens ugyldig accept (T t) metode udfører denne handling på argument t.
  • Det Fungere funktionel grænseflade repræsenterer en funktion, der accepterer et argument og returnerer et resultat. Dens R gælder (T t) metoden anvender denne funktion på argumentet t og returnerer resultatet.
  • Det Prædikat funktionel grænseflade repræsenterer en predikat (Boolsk værdi) af et argument. Dens boolsk test (T t) metode evaluerer dette prædikat på argumentet t og returnerer sandt eller falsk.
  • Det Leverandør funktionel grænseflade repræsenterer en leverandør af resultater. Dens T få () metoden modtager intet argument (er), men returnerer et resultat.

Det DaysInMonth ansøgning i liste 1 afslørede en komplet Fungere interface. Fra og med Java 8 kan du fjerne denne grænseflade og importere den identiske foruddefinerede Fungere interface.

Mere om foruddefinerede funktionelle grænseflader

"Kom godt i gang med lambda - udtryk i Java" giver eksempler på Forbruger og Prædikat funktionelle grænseflader. Tjek blogindlægget "Java 8 - Lazy argument evaluering" for at finde en interessant anvendelse til Leverandør.

Derudover præsenterer de foruddefinerede funktionelle grænseflader også nogle problemer. Blogger Pierre-Yves Saumont forklarer hvorfor.

Funktionelle API'er: Streams

Java 8 introducerede Streams API for at lette sekventiel og parallel behandling af dataelementer. Denne API er baseret på vandløb, hvor en strøm er en sekvens af elementer, der stammer fra en kilde og understøtter sekventielle og parallelle aggregatoperationer. EN kilde gemmer elementer (såsom en samling) eller genererer elementer (såsom en tilfældig talgenerator). En samlet er et resultat beregnet ud fra flere inputværdier.

En stream understøtter mellemliggende og terminaloperationer. En mellemdrift returnerer en ny stream, mens a terminal drift forbruger strømmen. Operationer er forbundet til en rørledning (via metoden kæde). Rørledningen starter med en kilde, der efterfølges af nul eller flere mellemliggende operationer, og slutter med en terminaloperation.

Strømme er et eksempel på en funktionel API. Det tilbyder filter, kort, reducering og andre genanvendelige førsteklasses funktioner. Jeg demonstrerede kort denne API i Medarbejdere applikation vist i del 1, oversigt 1. Liste 7 giver et andet eksempel.

Liste 7. Funktionel programmering med Streams (StreamFP.java)

importere java.util.Random; importere java.util.stream.IntStream; offentlig klasse 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", "Kairo", "Riyadh", "Moskva" }; IntStream.range (0, 11) .mapToObj (i -> byer [i]) .forEach (System.out :: println); System.out.println (); System.out.println (IntStream.range (0, 10). Reducere (0, (x, y) -> x + y)); System.out.println (IntStream.range (0, 10) .reduce (0, Integer :: sum)); }}

Det hoved () metoden opretter først en strøm af pseudorandom-heltal, der starter ved 0 og slutter ved 10. Strømmen er begrænset til nøjagtigt 10 heltal. Det filter() førsteklasses funktion modtager en lambda som sit predikatargument. Prædikatet fjerner ulige heltal fra strømmen. Endelig blev for hver() førsteklasses funktion udskriver hvert lige heltal til standardoutput via System.out :: println metodehenvisning.

Det hoved () metoden opretter derefter et heltalsstrøm, der producerer et sekventielt interval af heltal, der starter ved 0 og slutter ved 10. mapToObj () førsteklasses funktion modtager en lambda, der kortlægger et heltal til den tilsvarende streng ved heltalsindekset i byer array. Bynavnet sendes derefter til standardoutput via for hver() førsteklasses funktion og dens System.out :: println metodehenvisning.

Endelig hoved () demonstrerer reducere() førsteklasses funktion. En heltalstrøm, der producerer det samme interval af heltal som i det foregående eksempel, reduceres til en sum af deres værdier, som derefter udgives.

Identificering af mellem- og terminaloperationer

Hver af begrænse(), filter(), rækkevidde()og mapToObj () er mellemliggende operationer, hvorimod for hver() og reducere() er terminaloperationer.

Kompilér liste 7 som følger:

javac StreamFP.java

Kør den resulterende applikation som følger:

java StreamFP

Jeg observerede følgende output fra en kørsel:

0 2 10 6 0 8 10 New York London Paris Berlin BrasÌlia Tokyo Beijing Jerusalem Kairo Riyadh Moskva 45 45

Du har muligvis forventet 10 i stedet for 7 pseudorandom lige heltal (fra 0 til 10 takket være rækkevidde (0, 11)) vises i begyndelsen af ​​output. Trods alt, grænse (10) synes at indikere, at 10 heltal vil blive output. Dette er dog ikke tilfældet. Selvom grænse (10) resulterer i en strøm på nøjagtigt 10 heltal, filter (x -> x% 2 == 0) kald resulterer i, at ulige heltal fjernes fra strømmen.

Mere om Streams

Hvis du ikke er bekendt med Streams, skal du tjekke min tutorial, der introducerer Java SE 8s nye Streams API for mere om denne funktionelle API.

Afslutningsvis

Mange Java-udviklere vil ikke forfølge ren funktionel programmering på et sprog som Haskell, fordi det adskiller sig så meget fra det velkendte imperative, objektorienterede paradigme. Java 8s funktionelle programmeringsfunktioner er designet til at bygge bro over dette hul, hvilket gør det muligt for Java-udviklere at skrive kode, der er lettere at forstå, vedligeholde og teste. Funktionskode er også mere genanvendelig og mere velegnet til parallel behandling i Java. Med alle disse incitamenter er der virkelig ingen grund til ikke at inkorporere Java's funktionelle programmeringsmuligheder i din Java-kode.

Skriv en funktionel Bubble Sort-applikation

Funktionel tænkning er et udtryk opfundet af Neal Ford, der henviser til det kognitive skift fra det objektorienterede paradigme til det funktionelle programmeringsparadigme. Som du har set i denne vejledning, er det muligt at lære meget om funktionel programmering ved at omskrive objektorienteret kode ved hjælp af funktionelle teknikker.

Begræns det, du hidtil har lært, ved at besøge Sorteringsapplikationen fra oversigt 2. I dette hurtige tip viser jeg dig, hvordan du gør det skriv en rent funktionel boblesortering, først ved hjælp af præ-Java 8-teknikker og derefter ved hjælp af Java 8's funktionelle funktioner.

Denne historie, "Funktionel programmering til Java-udviklere, del 2" blev oprindeligt udgivet af JavaWorld.