Započnite s lambda izrazima na Javi

Prije Jave SE 8, anonimne su se klase obično koristile za prosljeđivanje funkcionalnosti metodi. Ova je praksa zamaglila izvorni kod, čineći ga težim za razumijevanje. Java 8 eliminirala je ovaj problem uvođenjem lambda. Ovaj tutorial prvo uvodi značajku lambda jezika, a zatim pruža detaljniji uvod u funkcionalno programiranje s lambda izrazima zajedno s ciljnim vrstama. Također ćete naučiti kako lambdas interakciju s opsezima, lokalne varijable, thisa superključne riječi i Java iznimke. 

Primijetite da su primjeri koda u ovom vodiču kompatibilni s JDK 12.

Otkrivanje vrsta za sebe

U ovom uputstvu neću uvoditi nikakve značajke ne-lambda jezika o kojima prethodno niste naučili, ali demonstrirat ću lambde putem tipova o kojima prethodno nisam raspravljao u ovoj seriji. Jedan od primjera je java.lang.Mathrazred. Ove ću vrste predstaviti u budućim vodičima za Java 101. Za sada predlažem da pročitate JDK 12 API dokumentaciju da biste saznali više o njima.

preuzimanje Preuzmite kod Preuzmite izvorni kod za primjere aplikacija u ovom vodiču. Stvorio Jeff Friesen za JavaWorld.

Lambda: Primer

Izraz lambda (X) opisuje blok koda (anonimni funkciju) koji se može prenijeti na konstruktorima i metode za naknadno izvršenje. Konstruktor ili metoda prima lambda kao argument. Razmotrimo sljedeći primjer:

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

Ovaj primjer identificira lambda za izlaz poruke u standardni izlazni tok. S lijeva na desno ()identificira lambda formalni popis parametara (u primjeru nema parametara), ->označava da je izraz lambda i System.out.println("Hello")kôd koji treba izvršiti.

Lambda pojednostavljuje upotrebu funkcionalnih sučelja , koja su komentirana sučelja koja svaka deklariraju točno jednu apstraktnu metodu (iako mogu prijaviti bilo koju kombinaciju zadanih, statičkih i privatnih metoda). Na primjer, standardna knjižnica klasa nudi java.lang.Runnablesučelje s jednom apstraktnom void run()metodom. Izjava ovog funkcionalnog sučelja prikazuje se u nastavku:

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

Biblioteka razreda bilježi Runnables @FunctionalInterface, što je primjer vrste java.lang.FunctionalInterfacebilješke. FunctionalInterfacekoristi se za označavanje onih sučelja koja će se koristiti u lambda kontekstima.

Lambda nema eksplicitnu vrstu sučelja. Umjesto toga, kompajler koristi okolni kontekst da zaključi koje funkcionalno sučelje treba izvesti kada je navedena lambda - lambda je vezana za to sučelje. Na primjer, pretpostavimo da sam odredio sljedeći fragment koda, koji prosljeđuje prethodnu lambdu kao argument konstruktoru java.lang.Threadklase Thread(Runnable target):

new Thread(() -> System.out.println("Hello"));

Kompajler utvrđuje da se lambda prosljeđuje Thread(Runnable r)jer je ovo jedini konstruktor koji zadovoljava lambda: Runnableje funkcionalno sučelje, prazan lambda popis formalnih parametara ()podudara se run()s praznim popisom parametara i vrste povratka ( void) također se slažu. Lambda je dužna Runnable.

Popis 1 predstavlja izvorni kod maloj aplikaciji koja vam omogućuje igranje s ovim primjerom.

Popis 1. LambdaDemo.java (verzija 1)

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

Sastavite popis 1 ( javac LambdaDemo.java) i pokrenite aplikaciju ( java LambdaDemo). Trebali biste promatrati sljedeći rezultat:

Hello

Lambdas može uvelike pojednostaviti količinu izvornog koda koji morate napisati, a može i puno olakšati razumijevanje izvornog koda. Na primjer, bez lambe, vjerojatno biste naveli detaljniji kod Popisa 2, koji se temelji na instanci anonimne klase koja se implementira Runnable.

Popis 2. LambdaDemo.java (verzija 2)

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

Nakon sastavljanja ovog izvornog koda, pokrenite aplikaciju. Otkrit ćete isti izlaz kao što je prethodno prikazano.

Lambda i API Streams

Osim što pojednostavljuju izvorni kod, lambda igraju važnu ulogu u Java-inom funkcionalno orijentiranom Streams API-u. Oni opisuju jedinice funkcionalnosti koje se prosljeđuju različitim API metodama.

Javanske lambde u dubini

Da biste učinkovito koristili lambde, morate razumjeti sintaksu lambda izraza zajedno s pojmom ciljne vrste. Također je potrebno da shvate kako lambdas interakciju s opsezima, lokalne varijable, thisi superriječi, i iznimaka. Sve ću ove teme pokriti u sljedećim odjeljcima.

Kako se primjenjuju lambde

Lambda se implementira u smislu invokedynamicuputa Java java.lang.invokeAPI-ja i API-ja. Pogledajte video Lambda: Zaviri ispod haube kako biste saznali više o lambda arhitekturi.

Lambda sintaksa

Svaka je lambda u skladu sa sljedećom sintaksom:

( formal-parameter-list ) -> { expression-or-statements }

Popis formal-parameter-listje formalnih parametara odvojenih zarezom, koji se moraju podudarati s parametrima pojedinačne apstraktne metode funkcionalnog sučelja u vrijeme izvođenja. Ako izostavite njihove tipove, prevodilac ih izvodi iz konteksta u kojem se koristi lambda. Razmotrite sljedeće primjere:

(double a, double b) // types explicitly specified (a, b) // types inferred by compiler

Lambda i var

Počevši od Jave SE 11, ime tipa možete zamijeniti s var. Na primjer, možete navesti (var a, var b).

Morate navesti zagrade za višestruke ili nikakve formalne parametre. Međutim, možete izostaviti zagrade (iako to ne morate) kada navodite jedan formalni parametar. (To se odnosi samo na naziv parametra - zagrade su potrebne kada je i vrsta navedena.) Razmotrite sljedeće dodatne primjere:

x // parentheses omitted due to single formal parameter (double x) // parentheses required because type is also present () // parentheses required when no formal parameters (x, y) // parentheses required because of multiple formal parameters

formal-parameter-listSlijedi ->znak, koji se nakon toga expression-or-statements--an ekspresijom ili blok od tvrdnji (ili je poznat kao lambda tijela). Za razliku od tijela koja se temelje na izrazima, tijela koja se temelje na izrazima moraju se postaviti između znakova zagrade open ( {) i close ( }):

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

Lambda tijelo zasnovano na ekspresiji prvog primjera ne mora biti postavljeno između zagrada. Drugi primjer pretvara tijelo temeljeno na izrazu u tijelo na temelju izraza, u kojem se returnmora navesti da bi se vratila vrijednost izraza. Posljednji primjer pokazuje više izjava i ne može se izraziti bez zagrada.

Lambda tijela i zarez

Obratite pažnju na odsutnost ili prisutnost zarez ( ;) u prethodnim primjerima. U svakom se slučaju lambda tijelo ne završava zarezom jer lambda nije izjava. Međutim, unutar lambda tijela na temelju izjave, svaka se izjava mora završiti zarezom i zarezom.

Popis 3 predstavlja jednostavnu aplikaciju koja pokazuje lambda sintaksu; imajte na umu da se ovaj popis temelji na prethodna dva primjera koda.

Popis 3. LambdaDemo.java (verzija 3)

@FunctionalInterface interface BinaryCalculator { double calculate(double value1, double value2); } @FunctionalInterface interface UnaryCalculator { double calculate(double value); } public class LambdaDemo { public static void main(String[] args) { System.out.printf("18 + 36.5 = %f%n", calculate((double v1, double v2) -> v1 + v2, 18, 36.5)); System.out.printf("89 / 2.9 = %f%n", calculate((v1, v2) -> v1 / v2, 89, 2.9)); System.out.printf("-89 = %f%n", calculate(v -> -v, 89)); System.out.printf("18 * 18 = %f%n", calculate((double v) -> v * v, 18)); } static double calculate(BinaryCalculator calc, double v1, double v2) { return calc.calculate(v1, v2); } static double calculate(UnaryCalculator calc, double v) { return calc.calculate(v); } }

Popis 3 prvo uvodi sučelja BinaryCalculatori i UnaryCalculatorfunkcionalna sučelja čije calculate()metode izvode proračune na dva ulazna argumenta, odnosno na jednom ulaznom argumentu. Ovaj popis također predstavlja LambdaDemoklasu čija main()metoda pokazuje ta funkcionalna sučelja.

The functional interfaces are demonstrated in the static double calculate(BinaryCalculator calc, double v1, double v2) and static double calculate(UnaryCalculator calc, double v) methods. The lambdas pass code as data to these methods, which are received as BinaryCalculator or UnaryCalculator instances.

Compile Listing 3 and run the application. You should observe the following output:

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

Target types

A lambda is associated with an implicit target type, which identifies the type of object to which a lambda is bound. The target type must be a functional interface that's inferred from the context, which limits lambdas to appearing in the following contexts:

  • Variable declaration
  • Assignment
  • Return statement
  • Array initializer
  • Method or constructor arguments
  • Lambda body
  • Ternary conditional expression
  • Cast expression

Listing 4 presents an application that demonstrates these target type contexts.

Popis 4. LambdaDemo.java (verzija 4)

import java.io.File; import java.io.FileFilter; import java.nio.file.Files; import java.nio.file.FileSystem; import java.nio.file.FileSystems; import java.nio.file.FileVisitor; import java.nio.file.FileVisitResult; import java.nio.file.Path; import java.nio.file.PathMatcher; import java.nio.file.Paths; import java.nio.file.SimpleFileVisitor; import java.nio.file.attribute.BasicFileAttributes; import java.security.AccessController; import java.security.PrivilegedAction; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.concurrent.Callable; public class LambdaDemo { public static void main(String[] args) throws Exception { // Target type #1: variable declaration Runnable r = () -> { System.out.println("running"); }; r.run(); // Target type #2: assignment r = () -> System.out.println("running"); r.run(); // Target type #3: return statement (in getFilter()) File[] files = new File(".").listFiles(getFilter("txt")); for (int i = 0; i  path.toString().endsWith("txt"), (path) -> path.toString().endsWith("java") }; FileVisitor visitor; visitor = new SimpleFileVisitor() { @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attribs) { Path name = file.getFileName(); for (int i = 0; i  System.out.println("running")).start(); // Target type #6: lambda body (a nested lambda) Callable callable = () -> () -> System.out.println("called"); callable.call().run(); // Target type #7: ternary conditional expression boolean ascendingSort = false; Comparator cmp; cmp = (ascendingSort) ? (s1, s2) -> s1.compareTo(s2) : (s1, s2) -> s2.compareTo(s1); List cities = Arrays.asList("Washington", "London", "Rome", "Berlin", "Jerusalem", "Ottawa", "Sydney", "Moscow"); Collections.sort(cities, cmp); for (int i = 0; i < cities.size(); i++) System.out.println(cities.get(i)); // Target type #8: cast expression String user = AccessController.doPrivileged((PrivilegedAction) () -> System.getProperty("user.name")); System.out.println(user); } static FileFilter getFilter(String ext) { return (pathname) -> pathname.toString().endsWith(ext); } }