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, this
a super
ključ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.Math
razred. 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.
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.Runnable
suč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 Runnable
s @FunctionalInterface
, što je primjer vrste java.lang.FunctionalInterface
bilješke. FunctionalInterface
koristi 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.Thread
klase 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: Runnable
je 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, this
i super
riječi, i iznimaka. Sve ću ove teme pokriti u sljedećim odjeljcima.
Kako se primjenjuju lambde
Lambda se implementira u smislu invokedynamic
uputa Java java.lang.invoke
API-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-list
je 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-list
Slijedi ->
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 return
mora 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 BinaryCalculator
i i UnaryCalculator
funkcionalna sučelja čije calculate()
metode izvode proračune na dva ulazna argumenta, odnosno na jednom ulaznom argumentu. Ovaj popis također predstavlja LambdaDemo
klasu č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); } }