Programmering

Kom godt i gang med lambda-udtryk i Java

Før Java SE 8 blev anonyme klasser typisk brugt til at overføre funktionalitet til en metode. Denne praksis tilslørede kildekoden, hvilket gør det sværere at forstå. Java 8 fjernede dette problem ved at introducere lambdas. Denne tutorial introducerer først lambda-sprogfunktionen og giver derefter en mere detaljeret introduktion til funktionel programmering med lambda-udtryk sammen med måltyper. Du lærer også, hvordan lambdas interagerer med rækkevidde, lokale variabler, det her og super nøgleord og Java-undtagelser.

Bemærk, at kodeeksempler i denne vejledning er kompatible med JDK 12.

Opdage typer for dig selv

Jeg introducerer ikke nogen ikke-lambda-sprogfunktioner i denne vejledning, som du ikke tidligere har lært om, men jeg vil demonstrere lambdas via typer, som jeg ikke tidligere har diskuteret i denne serie. Et eksempel er java.lang.Math klasse. Jeg vil introducere disse typer i fremtidige Java 101-tutorials. Indtil videre foreslår jeg at læse JDK 12 API-dokumentationen for at lære mere om dem.

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

Lambdas: En primer

EN lambda udtryk (lambda) beskriver en kodeblok (en anonym funktion), der kan overføres til konstruktører eller metoder til efterfølgende udførelse. Konstruktøren eller metoden modtager lambda som et argument. Overvej følgende eksempel:

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

Dette eksempel identificerer en lambda til udsendelse af en besked til standardoutputstrømmen. Fra venstre mod højre, () identificerer lambdas formelle parameterliste (der er ingen parametre i eksemplet), -> angiver, at udtrykket er en lambda, og System.out.println ("Hej") er den kode, der skal udføres.

Lambdas forenkler brugen af funktionelle grænseflader, som er kommenterede grænseflader, der hver erklærer nøjagtigt en abstrakt metode (selvom de også kan erklære enhver kombination af standard-, statiske og private metoder). For eksempel giver standardklassebiblioteket en java.lang.Køres interface med et enkelt abstrakt ugyldig kørsel () metode. Denne funktionelle grænseflades erklæring vises nedenfor:

@FunctionalInterface offentlig grænseflade Kan køres {public abstract void run (); }

Klassebiblioteket kommenterer Kan køres med @FunktionelInterface, som er en forekomst af java.lang.FunctionalInterface annoteringstype. FunctionalInterface bruges til at kommentere de grænseflader, der skal bruges i lambda-sammenhænge.

En lambda har ikke en eksplicit interface-type. I stedet bruger compileren den omgivende kontekst til at udlede, hvilken funktionel grænseflade der skal instantieres, når en lambda er specificeret - lambda er bundet til denne grænseflade. Antag for eksempel, at jeg specificerede følgende kodefragment, som sender den forrige lambda som et argument til java.lang.Tråd klassens Tråd (kørbart mål) konstruktør:

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

Kompilatoren bestemmer, at lambda overføres til Tråd (kørbar r) fordi dette er den eneste konstruktør, der tilfredsstiller lambda: Kan køres er en funktionel grænseflade, lambdas tomme formelle parameterliste () Tændstikker løb()tom parameterliste og returtyperne (ugyldig) er også enig. Lambda er bundet til Kan køres.

Liste 1 viser kildekoden til et lille program, der lader dig lege med dette eksempel.

Liste 1. LambdaDemo.java (version 1)

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

Kompilere lister 1 (javac LambdaDemo.java) og kør applikationen (java LambdaDemo). Du skal overholde følgende output:

Hej

Lambdas kan i høj grad forenkle mængden af ​​kildekode, du skal skrive, og kan også gøre kildekoden meget lettere at forstå. For eksempel uden lambdas ville du sandsynligvis angive Listing 2s mere detaljerede kode, som er baseret på en forekomst af en anonym klasse, der implementerer Kan køres.

Liste 2. LambdaDemo.java (version 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 (); }}

Efter kompilering af denne kildekode skal du køre applikationen. Du finder den samme output som tidligere vist.

Lambdas og Streams API

Ud over at forenkle kildekoden spiller lambdas en vigtig rolle i Java's funktionelt orienterede Streams API. De beskriver enheder af funktionalitet, der sendes til forskellige API-metoder.

Java lambdas i dybden

For at bruge lambdas effektivt skal du forstå syntaxen for lambda-udtryk sammen med begrebet måltype. Du skal også forstå, hvordan lambdas interagerer med rækkevidde, lokale variabler, det her og super nøgleord og undtagelser. Jeg vil dække alle disse emner i de efterfølgende afsnit.

Hvordan lambdas implementeres

Lambdas implementeres i form af Java-virtuelle maskins påkaldt dynamisk instruktion og java.lang.invoke API. Se videoen Lambda: A Peek Under the Hood for at lære om lambda-arkitektur.

Lambda syntaks

Hver lambda overholder følgende syntaks:

( formel-parameter-liste ) -> { udtryk eller udsagn }

Det formel-parameter-liste er en kommasepareret liste over formelle parametre, som skal matche parametrene for en funktionel grænseflades enkelt abstrakte metode ved kørsel. Hvis du udelader deres typer, udleder compileren disse typer fra den sammenhæng, hvor lambda bruges. Overvej følgende eksempler:

(dobbelt a, dobbelt b) // typer udtrykkeligt specificeret (a, b) // typer udledt af kompilatoren

Lambdas og var

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

Du skal angive parenteser til flere eller ingen formelle parametre. Du kan dog udelade parenteserne (selvom du ikke behøver det), når du angiver en enkelt formel parameter. (Dette gælder kun for parameternavnet - parenteser kræves, når typen også er specificeret.) Overvej følgende yderligere eksempler:

x // parentes udeladt på grund af en enkelt formel parameter (dobbelt x) // parentes krævet, fordi typen også er til stede () // parentes krævet, når der ikke kræves nogen formelle parametre (x, y) // parentes på grund af flere formelle parametre

Det formel-parameter-liste efterfølges af en -> token, som efterfølges af udtryk eller udsagn- et udtryk eller en blok med udsagn (enten er kendt som lambdas krop). I modsætning til ekspressionsbaserede kroppe skal sætningsbaserede kroppe placeres mellem åbne ({) og luk (}) afstivningstegn:

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

Det første eksempels ekspressionsbaserede lambda-krop behøver ikke placeres mellem seler. Det andet eksempel konverterer den ekspressionsbaserede krop til en sætningsbaseret krop, hvor Vend tilbage skal angives for at returnere udtrykkets værdi. Det sidste eksempel viser flere udsagn og kan ikke udtrykkes uden seler.

Lambda kroppe og semikoloner

Bemærk fraværet eller tilstedeværelsen af ​​semikolon (;) i de foregående eksempler. I begge tilfælde afsluttes lambdakroppen ikke med et semikolon, fordi lambda ikke er en erklæring. Imidlertid skal inden for en sætningsbaseret lambda-krop afsluttes med et semikolon.

Listing 3 præsenterer en enkel applikation, der demonstrerer lambda-syntaks; bemærk, at denne liste bygger på de foregående to kodeeksempler.

Liste 3. LambdaDemo.java (version 3)

@FunctionalInterface interface BinaryCalculator {dobbelt beregne (dobbelt værdi1, dobbelt værdi2); } @FunctionalInterface interface UnaryCalculator {dobbelt beregne (dobbelt værdi); } offentlig klasse LambdaDemo {offentlig statisk ugyldig hoved (String [] args) {System.out.printf ("18 + 36,5 =% f% n", beregne ((dobbelt v1, dobbelt 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 ((dobbelt v) -> v * v, 18)); } statisk dobbeltberegning (BinaryCalculator calc, double v1, double v2) {return calc.calculate (v1, v2); } statisk dobbeltberegning (UnaryCalculator calc, double v) {return calc.calculate (v); }}

Listing 3 introducerer først BinaryCalculator og UnaryCalculator funktionelle grænseflader, hvis Beregn() metoder udfører beregninger på henholdsvis to inputargumenter eller et enkelt inputargument. Denne liste introducerer også en LambdaDemo klasse hvis hoved () metode demonstrerer disse funktionelle grænseflader.

De funktionelle grænseflader er demonstreret i statisk dobbeltberegning (BinaryCalculator calc, dobbelt v1, dobbelt v2) og statisk dobbeltberegning (UnaryCalculator calc, dobbelt v) metoder. Lambdas overfører kode som data til disse metoder, som modtages som BinaryCalculator eller UnaryCalculator tilfælde.

Kompilér liste 3, og kør applikationen. Du skal overholde følgende output:

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

Måltyper

En lambda er forbundet med en implicit måltype, som identificerer den type objekt, som en lambda er bundet til. Måltypen skal være en funktionel grænseflade, der udledes af konteksten, hvilket begrænser lambdas til at blive vist i følgende sammenhænge:

  • Variabel erklæring
  • Opgave
  • Returerklæring
  • Initialisering af matrix
  • Metode eller konstruktørargumenter
  • Lambda krop
  • Ternært betinget udtryk
  • Cast udtryk

Listing 4 præsenterer et program, der demonstrerer disse måltypekontekster.

Liste 4. LambdaDemo.java (version 4)

import java.io.File; importere java.io.FileFilter; import java.nio.file.Files; importere java.nio.file.FileSystem; importere java.nio.file.FileSystems; import java.nio.file.FileVisitor; importere java.nio.file.FileVisitResult; import java.nio.file.Path; importer java.nio.file.PathMatcher; import java.nio.file.Paths; import java.nio.file.SimpleFileVisitor; import java.nio.file.attribute.BasicFileAttributter; importere java.security.AccessController; importere java.security.PrivilegedAction; importere java.util.Arrays; import java.util.Collections; import java.util.Comparator; importere java.util.List; import 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 nr. 2: tildeling r = () -> System.out.println ("kører"); r.run (); // Måltype # 3: returneringserklæring (i getFilter ()) File [] files = new File ("."). ListFiles (getFilter ("txt")); for (int i = 0; i path.toString (). endsWith ("txt"), (path) -> path.toString (). endsWith ("java")}; FileVisitor-besøgende; gæst = ny SimpleFileVisitor () { @Override public FileVisitResult visitFile (Sti-fil, BasicFileAttribute attributter) {Sti navn = fil.getFilenavn (); til (int i = 0; i System.out.println ("kører")). Start (); // Måltype # 6: lambda-krop (en indlejret lambda) Callable callable = () -> () -> System.out.println ("called"); callable.call (). Run (); // Måltype # 7: ternær betinget udtryk boolsk ascendingSort = false; Comparator cmp; cmp = (ascendingSort)? (s1, s2) -> s1.compareTo (s2): (s1, s2) -> s2.compareTo (s1); Liste byer = Arrays.asList ("Washington", "London", "Rom", "Berlin", "Jerusalem", "Ottawa", "Sydney", "Moskva"); Collections.sort (byer, cmp); for (int i = 0; i <towns.size (); i ++) System.out.println (towns.get (i)); // Måltype # 8: cast expression String user = AccessController.doPrivileged ((PrivilegedAction) () -> System.getProperty ("bruger.navn ")); System.out.println (bruger); } statisk FileFilter getFilter (String ext) {return (pathname) -> pathname.toString (). endsWith (ext); }}
$config[zx-auto] not found$config[zx-overlay] not found