Programmering

Kom i gang med lambdauttrykk i Java

Før Java SE 8 ble anonyme klasser vanligvis brukt til å overføre funksjonalitet til en metode. Denne praksisen forvirret kildekoden, noe som gjør det vanskeligere å forstå. Java 8 eliminerte dette problemet ved å introdusere lambdas. Denne opplæringen introduserer først lambdaspråkfunksjonen, og gir deretter en mer detaljert introduksjon til funksjonell programmering med lambdauttrykk sammen med måltyper. Du vil også lære hvordan lambdas samhandler med omfang, lokale variabler, dette og super søkeord og Java-unntak.

Merk at kodeeksempler i denne opplæringen er kompatible med JDK 12.

Oppdage typer for deg selv

Jeg vil ikke introdusere noen funksjoner som ikke er lambdaspråk i denne opplæringen som du ikke tidligere har lært om, men jeg vil demonstrere lambdas via typer som jeg ikke tidligere har diskutert i denne serien. Et eksempel er java.lang.Math klasse. Jeg vil introdusere disse typene i fremtidige Java 101-opplæringsprogrammer. Foreløpig foreslår jeg at du leser JDK 12 API-dokumentasjonen for å lære mer om dem.

last ned Få koden Last ned kildekoden for eksempel applikasjoner i denne opplæringen. Skapt av Jeff Friesen for JavaWorld.

Lambdas: En grunning

EN lambda uttrykk (lambda) beskriver en kodeblokk (en anonym funksjon) som kan overføres til konstruktører eller metoder for påfølgende utførelse. Konstruktøren eller metoden mottar lambda som et argument. Tenk på følgende eksempel:

() -> System.out.println ("Hei")

Dette eksemplet identifiserer en lambda for å sende ut en melding til standard utgangsstrøm. Fra venstre til høyre, () identifiserer lambdas formelle parameterliste (det er ingen parametere i eksemplet), -> indikerer at uttrykket er en lambda, og System.out.println ("Hei") er koden som skal utføres.

Lambdas forenkler bruken av funksjonelle grensesnitt, som er kommenterte grensesnitt som hver erklærer nøyaktig en abstrakt metode (selv om de også kan erklære en hvilken som helst kombinasjon av standard-, statiske og private metoder). For eksempel gir standard klassebiblioteket en java.lang.Runnable grensesnitt med et enkelt abstrakt ugyldig kjøre () metode. Dette funksjonelle grensesnittets erklæring vises nedenfor:

@FunctionalInterface public interface Runnable {public abstract void run (); }

Klassebiblioteket kommenterer Kjørbar med @FunctionalInterface, som er en forekomst av java.lang.FunctionalInterface merknadstype. FunctionalInterface brukes til å kommentere de grensesnittene som skal brukes i lambda-sammenhenger.

En lambda har ikke en eksplisitt grensesnitttype. I stedet bruker kompilatoren den omgivende konteksten for å utlede hvilket funksjonelt grensesnitt som skal startes når en lambda er spesifisert - lambda er bundet til det grensesnittet. Anta for eksempel at jeg spesifiserte følgende kodefragment, som sender den forrige lambda som et argument til java.lang.Tråd klassen Tråd (kjørbart mål) konstruktør:

ny tråd (() -> System.out.println ("Hei"));

Kompilatoren bestemmer at lambda blir overført til Tråd (Runnable r) fordi dette er den eneste konstruktøren som tilfredsstiller lambda: Kjørbar er et funksjonelt grensesnitt, lambdas tomme formelle parameterliste () fyrstikker løpe()sin tomme parameterliste og returtypene (tomrom) er også enig. Lambda er bundet til Kjørbar.

Oppføring 1 presenterer kildekoden til et lite program som lar deg spille med dette eksemplet.

Oppføring 1. LambdaDemo.java (versjon 1)

public class LambdaDemo {public static void main (String [] args) {new Thread (() -> System.out.println ("Hello")). start (); }}

Kompilere oppføring 1 (javac LambdaDemo.java) og kjør applikasjonen (java LambdaDemo). Du bør følge følgende utdata:

Hallo

Lambdas kan i stor grad forenkle mengden kildekode du må skrive, og kan også gjøre kildekoden mye lettere å forstå. Uten lambdas vil du for eksempel sannsynligvis spesifisere Listing 2s mer detaljerte kode, som er basert på en forekomst av en anonym klasse som implementerer Kjørbar.

Oppføring 2. LambdaDemo.java (versjon 2)

public class LambdaDemo {public static void main (String [] args) {Runnable r = new Runnable () {@Override public void run () {System.out.println ("Hello"); }}; ny tråd (r) .start (); }}

Etter å ha samlet denne kildekoden, kjør applikasjonen. Du vil oppdage samme utdata som tidligere vist.

Lambdas og Streams API

I tillegg til å forenkle kildekoden, spiller lambdas en viktig rolle i Java sin funksjonelt orienterte Streams API. De beskriver funksjonalitetsenheter som overføres til forskjellige API-metoder.

Java lambdas i dybden

For å bruke lambdas effektivt, må du forstå syntaksen til lambdauttrykk sammen med forestillingen om en måltype. Du må også forstå hvordan lambdas samhandler med omfang, lokale variabler, den dette og super nøkkelord og unntak. Jeg vil dekke alle disse emnene i avsnittene som følger.

Hvordan lambdas implementeres

Lambdas er implementert i forhold til den virtuelle Java-maskinen påkalt dynamisk instruksjon og java.lang.invoke API. Se videoen Lambda: A Peek Under the Hood for å lære om lambda-arkitektur.

Lambda syntaks

Hver lambda er i samsvar med følgende syntaks:

( formell-parameter-liste ) -> { uttrykk eller utsagn }

De formell-parameter-liste er en kommaseparert liste over formelle parametere, som må samsvare med parametrene til et funksjonelt grensesnittets enkle abstrakte metode ved kjøretid. Hvis du utelater typene deres, utleder kompilatoren disse typene fra konteksten som lambda brukes i. Tenk på følgende eksempler:

(doble a, doble b) // typer eksplisitt spesifiserte (a, b) // typer utledet av kompilatoren

Lambdas og var

Fra og med Java SE 11, kan du erstatte et typenavn med var. For eksempel kan du spesifisere (var a, var b).

Du må spesifisere parenteser for flere eller ingen formelle parametere. Du kan imidlertid utelate parentesene (selv om du ikke trenger det) når du spesifiserer en enkelt formell parameter. (Dette gjelder bare parameternavnet - parenteser kreves når typen også er spesifisert.) Tenk på følgende tilleggseksempler:

x // parentes utelatt på grunn av enkel formell parameter (dobbel x) // parentes kreves fordi typen også er til stede () // parentes kreves når ingen formelle parametere (x, y) // parentes kreves på grunn av flere formelle parametere

De formell-parameter-liste blir fulgt av en -> token, som følges av uttrykk eller utsagn- et uttrykk eller en blokk med utsagn (enten er kjent som lambdas kropp). I motsetning til uttrykksbaserte kropper, må setningsbaserte kropper plasseres mellom åpne ({) og lukk (}) avstivningstegn:

(dobbel radius) -> Math.PI * radius * radius radius -> {return Math.PI * radius * radius; } radius -> {System.out.println (radius); returner Math.PI * radius * radius; }

Det første eksemplets uttrykksbaserte lambdakropp trenger ikke å plasseres mellom seler. Det andre eksemplet konverterer den uttrykksbaserte kroppen til en uttalelsesbasert kropp, der komme tilbake må spesifiseres for å returnere uttrykkets verdi. Det siste eksemplet viser flere utsagn og kan ikke uttrykkes uten tannregulering.

Lambda-kropper og semikolon

Legg merke til fraværet eller tilstedeværelsen av semikolon (;) i de foregående eksemplene. I hvert tilfelle blir ikke lambdakroppen avsluttet med semikolon fordi lambda ikke er en uttalelse. Imidlertid, innenfor en uttalelsesbasert lambda-kropp, må hver uttalelse avsluttes med semikolon.

Listing 3 presenterer en enkel applikasjon som demonstrerer lambdasyntaks; Vær oppmerksom på at denne oppføringen bygger på de to foregående kodeeksemplene.

Oppføring 3. LambdaDemo.java (versjon 3)

@FunctionalInterface interface BinaryCalculator {dobbel beregne (dobbel verdi1, dobbel verdi2); } @FunctionalInterface interface UnaryCalculator {doble beregne (dobbel verdi); } offentlig klasse LambdaDemo {offentlig statisk ugyldig hoved (String [] args) {System.out.printf ("18 + 36,5 =% f% n", beregne ((dobbel v1, dobbel v2) -> v1 + v2, 18, 36,5)); System.out.printf ("89 / 2.9 =% f% n", beregne ((v1, v2) -> v1 / v2, 89, 2.9)); System.out.printf ("- 89 =% f% n", beregne (v -> -v, 89)); System.out.printf ("18 * 18 =% f% n", beregne ((dobbel v) -> v * v, 18)); } statisk dobbel beregning (BinaryCalculator calc, double v1, double v2) {return calc.calculate (v1, v2); } statisk dobbel beregning (UnaryCalculator calc, double v) {return calc.calculate (v); }}

Listing 3 introduserer først BinaryCalculator og UnaryCalculator funksjonelle grensesnitt hvis regne ut() metoder utfører beregninger på henholdsvis to inngangsargumenter eller på et enkelt inngangsargument. Denne oppføringen introduserer også en LambdaDemo klasse hvis hoved() metoden demonstrerer disse funksjonelle grensesnittene.

De funksjonelle grensesnittene er demonstrert i statisk dobbel beregning (BinaryCalculator calc, double v1, double v2) og statisk dobbeltberegning (UnaryCalculator calc, double v) metoder. Lambdas passerer kode som data til disse metodene, som mottas som BinaryCalculator eller UnaryCalculator tilfeller.

Kompilere oppføring 3 og kjør applikasjonen. Du bør følge følgende utdata:

18 + 36.5 = 54.500000 89 / 2.9 = 30.689655 -89 = -89.000000 18 * 18 = 324.000000

Måltyper

En lambda er assosiert med en implisitt måltype, som identifiserer typen objekt som en lambda er bundet til. Måletypen må være et funksjonelt grensesnitt som er utledet fra konteksten, som begrenser lambdas til å vises i følgende sammenhenger:

  • Variabel erklæring
  • Oppdrag
  • Returuttalelse
  • Initialiserende matrise
  • Metode- eller konstruktørargumenter
  • Lambda kropp
  • Ternært betinget uttrykk
  • Kaste uttrykk

Oppføring 4 presenterer et program som demonstrerer disse måltypekontekstene.

Oppføring 4. LambdaDemo.java (versjon 4)

importere java.io.File; importere java.io.FileFilter; importere java.nio.file.Files; importere java.nio.file.FileSystem; importere java.nio.file.FileSystems; importere java.nio.file.FileVisitor; importere java.nio.file.FileVisitResult; importere java.nio.file.Path; importere java.nio.file.PathMatcher; importere java.nio.file.Paths; importere java.nio.file.SimpleFileVisitor; importere java.nio.file.attribute.BasicFileAttribute; importere java.security.AccessController; importere java.security.PrivilegedAction; importere java.util.Arrays; importere java.util.Collections; importere java.util.Comparator; importere java.util.List; importer java.util.concurrent.Callable; public class LambdaDemo {public static void main (String [] args) throw Exception {// Target type # 1: variable declaration Runnable r = () -> {System.out.println ("running"); }; r.run (); // Måltype # 2: oppgave r = () -> System.out.println ("kjører"); r.run (); // Måltype # 3: returuttalelse (i getFilter ()) Fil [] filer = ny fil ("."). ListFiles (getFilter ("txt")); for (int i = 0; i path.toString (). endsWith ("txt"), (path) -> path.toString (). endsWith ("java")}; FileVisitor besøkende; besøkende = ny SimpleFileVisitor () { @Override public FileVisitResult visitFile (Path file, BasicFileAttributes attribs) {Path name = file.getFileName (); for (int i = 0; i System.out.println ("running")). Start (); // Måltype # 6: lambda-kropp (en nestet lambda) Callable callable = () -> () -> System.out.println ("called"); callable.call (). Run (); // Måltype # 7: ternary betinget uttrykk boolsk ascendingSort = false; Comparator cmp; cmp = (ascendingSort)? (s1, s2) -> s1.compareTo (s2): (s1, s2) -> s2.compareTo (s1); List towns = Arrays.asList ("Washington", "London", "Roma", "Berlin", "Jerusalem", "Ottawa", "Sydney", "Moskva"); Collections.sort (byer, cmp); for (int i = 0; i <towns.size (); i ++) System.out.println (cities.get (i)); // Måltype # 8: cast expression String user = AccessController.doPrivileged ((PrivilegedAction) () -> System.getProperty ("bruker.navn ")); System.out.println (bruker); } statisk FileFilter getFilter (strengutvidelse) {retur (stinavn) -> stinavn.tilString (). endsWith (utv); }}
$config[zx-auto] not found$config[zx-overlay] not found