Pred Java SE 8 so se anonimni razredi običajno uporabljali za posredovanje funkcionalnosti metodi. Ta praksa je zakrila izvorno kodo, zaradi česar je težje razumeti. Java 8 je to težavo odpravila z uvedbo lambdas. Ta vadnica najprej uvede funkcijo lambda jezika, nato pa podrobneje predstavi funkcionalno programiranje z lambda izrazi skupaj s ciljnimi vrstami. Naučili se boste tudi, kako lambde delujejo z obsegi, lokalnimi spremenljivkami to
in super
ključne besede in izjeme Java.
Upoštevajte, da so primeri kode v tej vadnici združljivi z JDK 12.
Odkrivanje vrst zase
V tej vadnici ne bom predstavil nobenih ne-lambda jezikovnih značilnosti, za katere še niste izvedeli, bom pa lambde predstavil s tipi, o katerih prej nisem razpravljal v tej seriji. En primer je java.lang.Math
razred. Te vrste bom predstavil v prihodnjih vajah o Javi 101. Za zdaj predlagam, da preberete dokumentacijo API-ja JDK 12, če želite izvedeti več o njih.
Lambde: Primer
A lambda izraz (lambda) opisuje blok kode (anonimna funkcija), ki se lahko posreduje konstruktorjem ali metodam za nadaljnje izvajanje. Konstruktor ali metoda prejme lambdo kot argument. Upoštevajte naslednji primer:
() -> System.out.println ("Pozdravljeni")
Ta primer identificira lambda za izpis sporočila v standardni izhodni tok. Od leve proti desni, ()
identificira lambda formalni seznam parametrov (v primeru ni parametrov), ->
označuje, da je izraz lambda, in System.out.println ("Pozdravljeni")
je koda, ki jo je treba izvršiti.
Lambda poenostavlja uporabo funkcionalni vmesniki, ki so pripomnjeni vmesniki, ki vsaka razglasi natančno eno abstraktno metodo (čeprav lahko prijavijo tudi katero koli kombinacijo privzetih, statičnih in zasebnih metod). Na primer, standardna knjižnica razredov ponuja java.lang.Teče
vmesnik z enim samim povzetkom void run ()
metoda. Izjava tega funkcionalnega vmesnika je prikazana spodaj:
@FunctionalInterface javni vmesnik Runnable {javni povzetek void run (); }
Knjižnica predavanj označuje Teče
s @FunctionalInterface
, ki je primerek java.lang.FunctionalInterface
vrsta pripisa. Funkcionalni vmesnik
se uporablja za označevanje tistih vmesnikov, ki bodo uporabljeni v lambda kontekstih.
Lambda nima eksplicitne vrste vmesnika. Namesto tega prevajalnik uporablja okoliški kontekst, da ugotovi, kateri funkcionalni vmesnik naj ustvari, ko je podana lambda - lambda je vezan na ta vmesnik. Denimo, da sem na primer navedel naslednji fragment kode, ki prejšnjo lambdo posreduje kot argument datoteki java.lang.Nit
razredov Navoj (cilj, ki ga je mogoče izvesti)
konstruktor:
nova nit (() -> System.out.println ("Pozdravljeni"));
Prevajalnik določi, da se prenaša lambda Navoj (izvedljivo r)
ker je to edini konstruktor, ki ustreza lambda: Teče
je funkcionalen vmesnik, lambda prazen seznam formalnih parametrov ()
tekme teči ()
prazen seznam parametrov in vrste vrnitve (praznino
) se tudi strinjajo. Lambda je zavezana Teče
.
Seznam 1 predstavlja izvorno kodo majhni aplikaciji, ki vam omogoča igranje s tem primerom.
Seznam 1. LambdaDemo.java (različica 1)
javni razred LambdaDemo {public static void main (String [] args) {new Thread (() -> System.out.println ("Hello")). start (); }}
Sestavi seznam 1 (javac LambdaDemo.java
) in zaženite aplikacijo (java LambdaDemo
). Upoštevati morate naslednje rezultate:
zdravo
Lambda lahko močno poenostavi količino izvorne kode, ki jo morate napisati, in lahko tudi veliko lažje razume izvorno kodo. Na primer, brez lambdas bi verjetno navedli bolj podrobno kodo na seznamu 2, ki temelji na primerku anonimnega razreda, ki izvaja Teče
.
Seznam 2. LambdaDemo.java (različica 2)
javni razred LambdaDemo {public static void main (String [] args) {Runnable r = new Runnable () {@Override public void run () {System.out.println ("Hello"); }}; nova nit (r) .start (); }}
Po prevajanju te izvorne kode zaženite aplikacijo. Odkrili boste enak izhod kot prej prikazan.
Lambda in API Streams
Poleg poenostavitve izvorne kode imajo lambde pomembno vlogo v Java-jevem funkcionalno usmerjenem API-ju Streams. Opisujejo enote funkcionalnosti, ki se posredujejo različnim metodam API.
Java lambdas v globino
Za učinkovito uporabo lambdas morate razumeti sintakso lambda izrazov skupaj s pojmom ciljne vrste. Razumeti morate tudi, kako lambde vplivajo na obseg, lokalne spremenljivke, to
in super
ključne besede in izjeme. Vse te teme bom obravnaval v naslednjih poglavjih.
Kako se izvajajo lambde
Lambda se izvaja v smislu Java navideznih strojev invokedynamic
navodila in java.lang.invoke
API. Oglejte si video Lambda: Peek Under the Hood, če želite izvedeti več o lambda arhitekturi.
Lambda sintaksa
Vsaka lambda je v skladu z naslednjo skladnjo:
( seznam formalnih parametrov ) -> { izraz ali izjave }
The seznam formalnih parametrov
je seznam formalnih parametrov, ločenih z vejicami, ki se morajo med izvajanjem ujemati s parametri posamezne abstraktne metode funkcionalnega vmesnika. Če izpustite njihove tipe, jih prevajalnik sklepa iz konteksta, v katerem se uporablja lambda. Upoštevajte naslednje primere:
(double a, double b) // tipi, ki so izrecno določeni (a, b) // vrste, ki jih je določil prevajalnik
Lambda in var
Začenši z Javo SE 11, lahko ime tipa nadomestite z var
. Lahko na primer določite (var a, var b)
.
Navesti morate oklepaje za več ali nobenih formalnih parametrov. Vendar lahko oklepaje izpustite (čeprav vam ni treba) pri podajanju enega samega formalnega parametra. (To velja samo za ime parametra - oklepaji so obvezni, če je naveden tudi tip.) Upoštevajte naslednje dodatne primere:
x // oklepaji izpuščeni zaradi enojnega formalnega parametra (dvojni x) // zahtevajo se oklepaji, ker je prisoten tudi tip () // oklepaji so potrebni, kadar zaradi več formalnih parametrov niso potrebni formalni parametri (x, y)
The seznam formalnih parametrov
sledi a ->
žeton, ki mu sledi izraz ali izjave
- izraz ali blok stavkov (bodisi je znano kot telo lambde). Za razliko od teles, ki temeljijo na izrazu, morajo biti telesa, ki temeljijo na izjavah, postavljena med odprta ({
) in zaprite (}
) oklepaji:
(dvojni polmer) -> Math.PI * polmer * radij polmera -> {vrni Math.PI * polmer * polmer; } polmer -> {System.out.println (radij); vrnitev Math.PI * polmer * polmer; }
Telesa lambda, ki temelji na izrazu prvega primera, ni treba postaviti med oklepaje. Drugi primer pretvori telo, ki temelji na izrazu, v telo, ki temelji na stavku, v katerem vrnitev
mora biti podan, da vrne vrednost izraza. Končni primer prikazuje več izjav in jih ni mogoče izraziti brez oklepajev.
Lambda telesa in podpičja
Upoštevajte odsotnost ali prisotnost podpičja (;
) v prejšnjih primerih. V obeh primerih se telo lambde ne zaključi s podpičjem, ker lambda ni stavek. V lambda telesu, ki temelji na stavku, pa je treba vsak stavek zaključiti s podpičjem.
Seznam 3 predstavlja preprosto aplikacijo, ki prikazuje lambda sintakso; upoštevajte, da ta seznam temelji na prejšnjih dveh primerih kode.
Seznam 3. LambdaDemo.java (različica 3)
@FunctionalInterface vmesnik BinaryCalculator {dvojni izračun (dvojna vrednost1, dvojna vrednost2); } @FunctionalInterface vmesnik UnaryCalculator {dvojni izračun (dvojna vrednost); } javni razred LambdaDemo {public static void main (String [] args) {System.out.printf ("18 + 36,5 =% f% n", izračunaj ((dvojni v1, dvojni v2) -> v1 + v2, 18, 36,5)); System.out.printf ("89 / 2,9 =% f% n", izračunaj ((v1, v2) -> v1 / v2, 89, 2,9)); System.out.printf ("- 89 =% f% n", izračunaj (v -> -v, 89)); System.out.printf ("18 * 18 =% f% n", izračunaj ((dvojno v) -> v * v, 18)); } statični dvojni izračun (BinaryCalculator calc, double v1, double v2) {return calc.calculate (v1, v2); } statični dvojni izračun (UnaryCalculator calc, double v) {return calc.calculate (v); }}
Seznam 3 najprej predstavlja BinaryCalculator
in UnaryCalculator
funkcionalni vmesniki, katerih izračunaj ()
metode izvajajo izračune na dveh vhodnih argumentih oziroma na enem vhodnem argumentu. Ta seznam predstavlja tudi a LambdaDemo
razred, katerega glavni ()
metoda prikazuje te funkcionalne vmesnike.
Funkcionalni vmesniki so predstavljeni v statični dvojni izračun (izračun BinaryCalculator, dvojni v1, dvojni v2)
in statični dvojni izračun (izračun UnaryCalculator, dvojni v)
metode. Lambde posredujejo kodo kot podatke tem metodam, ki jih prejmejo kot BinaryCalculator
ali UnaryCalculator
primerov.
Sestavite seznam 3 in zaženite aplikacijo. Upoštevati morate naslednje rezultate:
18 + 36.5 = 54.500000 89 / 2.9 = 30.689655 -89 = -89.000000 18 * 18 = 324.000000
Vrste ciljev
Lambda je povezana z implicitnim ciljni tip, ki identificira vrsto predmeta, na katerega je vezana lambda. Ciljni tip mora biti funkcionalni vmesnik, ki izhaja iz konteksta, ki omejuje lambde na pojav v naslednjih kontekstih:
- Deklaracija spremenljivke
- Dodelitev
- Izjava o vrnitvi
- Pobudnik matrike
- Argumenti metode ali konstruktorja
- Lambda telo
- Ternarni pogojni izraz
- Igrani izraz
V seznamu 4 je predstavljena aplikacija, ki prikazuje te kontekste ciljne vrste.
Seznam 4. LambdaDemo.java (različica 4)
import java.io.File; uvoz java.io.FileFilter; uvoz java.nio.file.Files; uvoz java.nio.file.FileSystem; uvoz java.nio.file.FileSystems; uvoz java.nio.file.FileVisitor; uvoz java.nio.file.FileVisitResult; uvoz java.nio.file.Path; uvoz java.nio.file.PathMatcher; uvoz java.nio.file.Paths; uvoz java.nio.file.SimpleFileVisitor; uvoz java.nio.file.attribute.BasicFileAttributes; uvoz java.security.AccessController; uvoz java.security.PrivilegedAction; uvoz java.util.Arrays; uvoz java.util.Collections; uvoz java.util.Comparator; uvoz java.util.List; uvoz java.util.concurrent.Callable; javni razred LambdaDemo {public static void main (String [] args) vrže izjemo {// Tip cilja # 1: deklaracija spremenljivke Runnable r = () -> {System.out.println ("teče"); }; r.run (); // Tip cilja # 2: dodelitev r = () -> System.out.println ("teče"); r.run (); // Tip cilja # 3: vrnitev stavka (v getFilter ()) File [] files = nova datoteka ("."). ListFiles (getFilter ("txt")); for (int i = 0; i path.toString (). ENDWith ("txt"), (path) -> path.toString (). EndWith ("java")}; Obiskovalec FileVisitor; Obiskovalec = nov SimpleFileVisitor () { @Override public FileVisitResult visitFile (datoteka poti, atributi BasicFileAttributes) {Ime poti = file.getFileName (); for (int i = 0; i System.out.println ("running")). Start (); // Vrsta cilja # 6: lambda telo (ugnezdena lambda) Callable callable = () -> () -> System.out.println ("pozvan"); callable.call (). Run (); // Tip cilja # 7: ternarni pogojni izraz boolean ascendingSort = false; primerjalnik cmp; cmp = (ascendingSort)? (s1, s2) -> s1.compareTo (s2): (s1, s2) -> s2.compareTo (s1); Seznam mest = Arrays.asList ("Washington", "London", "Rim", "Berlin", "Jeruzalem", "Ottawa", "Sydney", "Moskva"); Collections.sort (mest, cmp); za (int i = 0; i <cities.size (); i ++) System.out.println (city.get (i)); // Tip cilja # 8: izraz izraza String user = AccessController.doPrivileged ((PrivilegedAction) () -> System.getProperty ("uporabniško.ime ")); System.out.println (uporabnik); } static FileFilter getFilter (String ext) {return (pathname) -> pathname.toString (). EndWith (ext); }}