Programmering

Funksjonell programmering for Java-utviklere, del 2

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 20henholdsvis) 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:

  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)

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ørbars 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.outs 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 sverdi 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.outs 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 :: toStringJeg 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 er ugyldig godta (T t) metoden utfører denne operasjonen på argument t.
  • De Funksjon funksjonelt grensesnitt representerer en funksjon som godtar ett argument og returnerer et resultat. Det er R gjelder (T t) metoden bruker denne funksjonen til argument t og returnerer resultatet.
  • De Predikere funksjonelt grensesnitt representerer en predikat (Funksjon som er verdsatt av Boolean) av ett argument. Det er boolsk test (T t) metoden evaluerer dette predikatet på argumentet t og returnerer sant eller usant.
  • De Leverandør funksjonelt grensesnitt representerer en leverandør av resultater. Det er T 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.

$config[zx-auto] not found$config[zx-overlay] not found