Programování

Začněte s výrazy lambda v Javě

Před Java SE 8 se k předání funkčnosti metodě obvykle používaly anonymní třídy. Tato praxe zmatená zdrojový kód, takže je těžší pochopit. Java 8 odstranil tento problém zavedením lambdas. Tento kurz nejprve představuje funkci jazyka lambda, poté poskytuje podrobnější úvod do funkčního programování pomocí výrazů lambda spolu s cílovými typy. Dozvíte se také, jak lambdy interagují s rozsahy, lokálními proměnnými, tento a super klíčová slova a Java výjimky.

Všimněte si, že příklady kódu v tomto kurzu jsou kompatibilní s JDK 12.

Objevování typů pro sebe

V tomto tutoriálu nebudu představovat žádné jazykové funkce jiné než lambda, o kterých jste se dříve nedozvěděli, ale předvedu lambdas prostřednictvím typů, o kterých jsem v této sérii dříve nepojednával. Jedním z příkladů je java.lang.Math třída. Tyto typy představím v budoucích výukových programech Java 101. Prozatím doporučuji přečíst si dokumentaci API JDK 12, abyste se o nich dozvěděli více.

stáhnout Získat kód Stáhněte si zdrojový kód například pro aplikace v tomto výukovém programu. Vytvořil Jeff Friesen pro JavaWorld.

Lambdas: Základní nátěr

A výraz lambda (lambda) popisuje blok kódu (anonymní funkce), který lze předat konstruktorům nebo metodám pro následné provedení. Konstruktor nebo metoda přijímá lambda jako argument. Zvažte následující příklad:

() -> System.out.println ("Dobrý den")

Tento příklad identifikuje lambdu pro výstup zprávy do standardního výstupního proudu. Zleva doprava, () identifikuje formální seznam parametrů lambda (v příkladu nejsou žádné parametry), -> označuje, že výraz je lambda, a System.out.println ("Dobrý den") je kód, který má být proveden.

Lambdy zjednodušují používání funkční rozhraní, což jsou anotovaná rozhraní, z nichž každé deklaruje přesně jednu abstraktní metodu (i když také mohou deklarovat libovolnou kombinaci výchozích, statických a soukromých metod). Například knihovna standardních tříd poskytuje a java.lang.Runnable rozhraní s jediným abstraktem neplatný běh () metoda. Deklarace tohoto funkčního rozhraní se zobrazí níže:

@FunctionalInterface veřejné rozhraní Runnable {public abstract void run (); }

Knihovna tříd anotuje Spustitelný s @Funkčnírozhraní, což je instance java.lang.FunctionalInterface typ poznámky. Funkční rozhraní se používá k anotaci těch rozhraní, která se mají použít v kontextech lambda.

Lambda nemá explicitní typ rozhraní. Místo toho kompilátor používá okolní kontext k odvození funkčního rozhraní k vytvoření instance, když je zadána lambda - lambda je vázaný k tomuto rozhraní. Předpokládejme například, že jsem zadal následující fragment kódu, který předá předchozí lambda jako argument do java.lang.Thread třídy Vlákno (spustitelný cíl) konstruktor:

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

Kompilátor určuje, že se předává lambda Vlákno (Runnable r) protože toto je jediný konstruktor, který splňuje lambda: Spustitelný je funkční rozhraní, prázdný formální seznam parametrů lambda () zápasy běh()prázdný seznam parametrů a návratové typy (prázdnota) také souhlasím. Lambda je vázána na Spustitelný.

Výpis 1 představuje zdrojový kód malé aplikace, která vám umožní hrát s tímto příkladem.

Výpis 1. LambdaDemo.java (verze 1)

veřejná třída LambdaDemo {public static void main (String [] args) {new Thread (() -> System.out.println ("Hello")). start (); }}

Zkompilovat výpis 1 (javac LambdaDemo.java) a spusťte aplikaci (java LambdaDemo). Měli byste dodržovat následující výstup:

Ahoj

Lambdas může výrazně zjednodušit množství zdrojového kódu, který musíte napsat, a také může výrazně usnadnit pochopení zdrojového kódu. Například bez lambdas byste pravděpodobně zadali podrobnější kód Výpisu 2, který je založen na instanci anonymní třídy, která implementuje Spustitelný.

Výpis 2. LambdaDemo.java (verze 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 (); }}

Po kompilaci tohoto zdrojového kódu spusťte aplikaci. Objevíte stejný výstup, jaký byl dříve zobrazen.

Lambdas and the Streams API

Kromě zjednodušení zdrojového kódu hrají lambdas důležitou roli ve funkčně orientovaném Java Streams API. Popisují jednotky funkcí, které jsou předávány různým metodám API.

Java lambdas do hloubky

Chcete-li lambdas používat efektivně, musíte pochopit syntaxi výrazů lambda spolu s představou cílového typu. Musíte také pochopit, jak lambdy interagují s obory, lokálními proměnnými, tento a super klíčová slova a výjimky. Všechna tato témata proberu v následujících částech.

Jak jsou lambdy implementovány

Lambdy jsou implementovány z hlediska virtuálního stroje Java invokedynamic instrukce a java.lang.invoke API. Podívejte se na video Lambda: Peek Under the Hood, kde se dozvíte více o architektuře lambda.

Lambda syntaxe

Každá lambda odpovídá následující syntaxi:

( formální seznam parametrů ) -> { výraz-nebo-prohlášení }

The formální seznam parametrů je seznam formálních parametrů oddělených čárkami, který se musí za běhu shodovat s parametry jedné abstraktní metody funkčního rozhraní. Pokud vynecháte jejich typy, kompilátor odvozuje tyto typy z kontextu, ve kterém se používá lambda. Zvažte následující příklady:

(double a, double b) // typy výslovně specifikované (a, b) // typy odvozené kompilátorem

Lambdas a var

Počínaje Java SE 11 můžete nahradit název typu var. Můžete například určit (var a, var b).

Musíte zadat závorky pro více nebo žádné formální parametry. Při zadávání jediného formálního parametru však můžete vynechat závorky (i když nemusíte). (To platí pouze pro název parametru - závorky jsou povinné, když je zadán také typ.) Zvažte následující další příklady:

x // závorky vynechány kvůli jednomu formálnímu parametru (dvojité x) // závorky povinné, protože je přítomen také typ () // závorky povinné, když nejsou žádné formální parametry (x, y) // závorky nutné kvůli více formálním parametrům

The formální seznam parametrů následuje a -> token, za kterým následuje výraz-nebo-prohlášení- výraz nebo blok příkazů (jeden z nich je známý jako tělo lambda). Na rozdíl od orgánů založených na výrazech musí být těla založená na prohlášení umístěna mezi otevřená ({) a zavřít (}) složené znaky:

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

Tělo lambda založené na výrazu prvního příkladu nemusí být umístěno mezi závorky. Druhý příklad převádí tělo založené na výrazech na tělo založené na příkazech, ve kterém vrátit se musí být zadán pro vrácení hodnoty výrazu. Poslední příklad ukazuje více příkazů a nelze jej vyjádřit bez složených závorek.

Lambda těla a středníky

Všimněte si nepřítomnosti nebo přítomnosti středníků (;) v předchozích příkladech. V každém případě není tělo lambda ukončeno středníkem, protože lambda není příkaz. V rámci těla lambda založeného na příkazech však musí být každý příkaz ukončen středníkem.

Výpis 3 představuje jednoduchou aplikaci, která demonstruje syntaxi lambda; Všimněte si, že tento výpis vychází z předchozích dvou příkladů kódu.

Výpis 3. LambdaDemo.java (verze 3)

@FunctionalInterface interface BinaryCalculator {dvojitý výpočet (dvojitá hodnota1, dvojitá hodnota2); } @FunctionalInterface interface UnaryCalculator {dvojitý výpočet (dvojitá hodnota); } veřejná třída LambdaDemo {public static void main (String [] args) {System.out.printf ("18 + 36,5 =% f% n", vypočítat ((double v1, double v2) -> v1 + v2, 18, 36,5)); System.out.printf ("89 / 2,9 =% f% n", vypočítat ((v1, v2) -> v1 / v2, 89, 2,9)); System.out.printf ("- 89 =% f% n", vypočítat (v -> -v, 89)); System.out.printf ("18 * 18 =% f% n", vypočítat ((double v) -> v * v, 18)); } statický dvojitý výpočet (BinaryCalculator calc, double v1, double v2) {return calc.calculate (v1, v2); } statický dvojitý výpočet (UnaryCalculator calc, dvojitý v) {návrat calc.calculate (v); }}

Výpis 3 nejprve představuje BinaryCalculator a UnaryCalculator funkční rozhraní, jejichž vypočítat() metody provádějí výpočty na dvou vstupních argumentech nebo na jednom vstupním argumentu. Tento seznam také zavádí a LambdaDemo třída, jejíž hlavní() metoda demonstruje tato funkční rozhraní.

Funkční rozhraní jsou předvedena v dokumentu statický dvojitý výpočet (BinaryCalculator calc, double v1, double v2) a statický dvojitý výpočet (UnaryCalculator calc, double v) metody. Lambdy předávají kód jako data těmto metodám, které jsou přijímány jako BinaryCalculator nebo UnaryCalculator instance.

Zkompilujte výpis 3 a spusťte aplikaci. Měli byste dodržovat následující výstup:

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

Typy cílů

Lambda je spojena s implicitní typ cíle, který identifikuje typ objektu, ke kterému je lambda vázána. Cílovým typem musí být funkční rozhraní odvozené z kontextu, který omezuje zobrazení lambdas v následujících kontextech:

  • Proměnná deklarace
  • Úkol
  • Prohlášení o vrácení
  • Inicializátor pole
  • Argumenty metody nebo konstruktoru
  • Lambda tělo
  • Ternární podmíněný výraz
  • Obsazení výrazu

Výpis 4 představuje aplikaci, která demonstruje tyto kontexty cílového typu.

Výpis 4. LambdaDemo.java (verze 4)

import java.io.File; import java.io.FileFilter; import java.nio.file.Files; importovat java.nio.file.FileSystem; import java.nio.file.FileSystems; import java.nio.file.FileVisitor; import java.nio.file.FileVisitResult; importovat java.nio.file.Path; import java.nio.file.PathMatcher; import java.nio.file.Paths; importovat java.nio.file.SimpleFileVisitor; import java.nio.file.attribute.BasicFileAttributes; import java.security.AccessController; importovat 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 (); // Cílový typ # 2: přiřazení r = () -> System.out.println ("běžící"); r.run (); // Cílový typ # 3: návratový příkaz (v getFilter ()) Soubor [] soubory = nový Soubor ("."). 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 (soubor cesty, atributy BasicFileAttributes) {název cesty = soubor.getFileName (); pro (int i = 0; i System.out.println ("běžící")). Start (); // typ cíle # 6: lambda body (a nested lambda) Callable callable = () -> () -> System.out.println ("called"); callable.call (). Run (); // Cílový typ # 7: ternární podmíněný výraz boolean ascendingSort = false; komparátor cmp; cmp = (ascendingSort)? (s1, s2) -> s1.compareTo (s2): (s1, s2) -> s2.compareTo (s1); seznam měst = Arrays.asList („Washington“, „Londýn“, „Řím“, „Berlín“, „Jeruzalém“, „Ottawa“, „Sydney“, „Moskva“); Collections.sort (města, cmp); pro (int i = 0; i <cities.size (); i ++) System.out.println (cities.get (i)); // Cílový typ # 8: cast výraz String user = AccessController.doPrivileged ((PrivilegedAction) () -> System.getProperty ("user.name ")); System.out.println (uživatel); } statický FileFilter getFilter (řetězec ext) {návrat (cesta) -> cesta.toString (). endsWith (ext); }}