Programovanie

Začnite s výrazmi lambda v jazyku Java

Pred programom Java SE 8 sa na odovzdanie funkčnosti metóde zvyčajne používali anonymné triedy. Táto prax zahmlila zdrojový kód, čo sťažilo jeho pochopenie. Java 8 eliminovala tento problém zavedením lambdas. Tento výukový program najskôr predstavuje funkciu jazyka lambda, potom poskytuje podrobnejší úvod do funkčného programovania pomocou výrazov lambda spolu s cieľovými typmi. Dozviete sa tiež, ako lambdy interagujú s rozsahmi, lokálnymi premennými, toto a Super kľúčové slová a výnimky Java.

Upozorňujeme, že príklady kódov v tomto tutoriále sú kompatibilné s JDK 12.

Objavujte typy pre seba

V tomto tutoriále nebudem predstavovať žiadne jazykové funkcie iné ako lambda, o ktorých ste sa predtým nedozvedeli, ale ukážem lambdy pomocou typov, o ktorých som predtým v tejto sérii nehovoril. Jedným z príkladov je java.lang.Math trieda. Tieto typy predstavím v budúcich výukových programoch Java 101. Zatiaľ navrhujem prečítať si dokumentáciu JDK 12 API, aby ste sa o nich dozvedeli viac.

stiahnuť Získajte kód Stiahnite si zdrojový kód napríklad pre aplikácie v tejto príručke. Vytvoril Jeff Friesen pre JavaWorld.

Lambdas: Základný náter

A výraz lambda (lambda) popisuje blok kódu (anonymná funkcia), ktorý je možné odovzdať konštruktérom alebo metódam na následné vykonanie. Konštruktor alebo metóda prijme lambdu ako argument. Uvažujme o nasledujúcom príklade:

() -> System.out.println („ahoj“)

Tento príklad identifikuje lambdu na výstup správy do štandardného výstupného toku. Zľava doprava () identifikuje formálny zoznam parametrov lambda (v príklade nie sú žiadne parametre), -> označuje, že výraz je lambda, a System.out.println („ahoj“) je kód, ktorý sa má vykonať.

Lambdas zjednodušuje používanie funkčné rozhrania, čo sú anotované rozhrania, z ktorých každé deklaruje presne jednu abstraktnú metódu (aj keď môžu deklarovať aj ľubovoľnú kombináciu predvolených, statických a súkromných metód). Napríklad štandardná knižnica triedy poskytuje a java.lang.Runnable rozhranie s jediným abstraktom void run () metóda. Deklarácia tohto funkčného rozhrania sa zobrazuje nižšie:

@FunctionalInterface verejné rozhranie Runnable {public abstract void run (); }

Knižnica triedy je anotovaná Spustiteľné s @Funkčné rozhranie, ktorý je inštanciou java.lang.Funkčné rozhranie typ anotácie. Funkčné rozhranie sa používa na anotáciu tých rozhraní, ktoré sa majú použiť v kontextoch lambda.

Lambda nemá explicitný typ rozhrania. Namiesto toho kompilátor pomocou okolitého kontextu odvodzuje, ktoré funkčné rozhranie má vytvoriť inštanciu, keď je zadaná lambda - lambda je viazaný na toto rozhranie. Predpokladajme napríklad, že som zadal nasledujúci fragment kódu, ktorý predá predchádzajúcu lambdu ako argument do java.lang.Thread triedy Vlákno (spustiteľný cieľ) konštruktér:

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

Kompilátor určí, že sa odovzdáva lambda Vlákno (spustiteľné r) pretože toto je jediný konštruktor, ktorý spĺňa lambda: Spustiteľné je funkčné rozhranie, prázdny formálny zoznam parametrov lambda () zápasy run ()prázdny zoznam parametrov a návratové typy (neplatný) tiež súhlasím. Lambda je viazaná na Spustiteľné.

Výpis 1 predstavuje zdrojový kód malej aplikácie, ktorá vám umožní hrať sa s týmto príkladom.

Zoznam 1. LambdaDemo.java (verzia 1)

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

Zostaviť zoznam 1 (javac LambdaDemo.java) a spustite aplikáciu (java LambdaDemo). Mali by ste dodržiavať nasledujúci výstup:

Ahoj

Lambdas môže výrazne zjednodušiť množstvo zdrojového kódu, ktoré musíte napísať, a tiež môže výrazne uľahčiť jeho pochopenie. Napríklad bez lambdas by ste pravdepodobne zadali podrobnejší kód záznamu 2, ktorý je založený na inštancii anonymnej triedy, ktorá implementuje Spustiteľné.

Zoznam 2. LambdaDemo.java (verzia 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 kompilácii tohto zdrojového kódu spustite aplikáciu. Objavíte rovnaký výstup ako predtým.

Lambdas a API Streams

Rovnako ako zjednodušenie zdrojového kódu, lambdas hrá dôležitú úlohu vo funkčne orientovanom Java Streams API. Opisujú jednotky funkčnosti, ktoré sa prenášajú do rôznych metód API.

Java lambdas do hĺbky

Aby ste mohli efektívne používať lambdu, musíte porozumieť syntaxi výrazov lambda spolu s predstavou cieľového typu. Musíte tiež pochopiť, ako lambdy interagujú s rozsahmi, lokálnymi premennými, toto a Super kľúčové slová a výnimky. Všetky tieto témy rozoberiem v nasledujúcich častiach.

Ako sa implementujú lambdy

Lambdy sú implementované z hľadiska virtuálneho stroja Java invokedynamický inštrukcie a java.lang.invoke API. Pozrite si video Lambda: Nahliadnite pod kapotu a dozviete sa viac o architektúre lambda.

Lambda syntax

Každá lambda zodpovedá nasledujúcej syntaxi:

( formálny zoznam parametrov ) -> { výraz-alebo-výroky }

The formálny zoznam parametrov je zoznam formálnych parametrov oddelených čiarkou, ktorý sa za behu musí zhodovať s parametrami jednej abstraktnej metódy funkčného rozhrania. Ak vynecháte ich typy, kompilátor odvodí tieto typy z kontextu, v ktorom sa používa lambda. Zvážte nasledujúce príklady:

(double a, double b) // typy výslovne uvedené (a, b) // typy odvodené kompilátorom

Lambdas a var

Počnúc jazykom Java SE 11 môžete nahradiť názov typu var. Môžete napríklad určiť (var a, var b).

Musíte zadať zátvorky pre viacnásobné alebo žiadne formálne parametre. Pri zadávaní jedného formálneho parametra však môžete vynechať zátvorky (aj keď nemusíte). (Toto platí iba pre názov parametra - pri zadaní typu je potrebné uviesť zátvorky.) Zvážte nasledujúce ďalšie príklady:

x // zátvorky vynechané kvôli jednému formálnemu parametru (dvojité x) // zátvorky požadované, pretože je prítomný aj typ () // zátvorky požadované, keď nie sú potrebné žiadne formálne parametre (x, y) // zátvorky požadované z dôvodu viacerých formálnych parametrov

The formálny zoznam parametrov nasleduje a -> žetón, za ktorým nasleduje výraz-alebo-výroky- výraz alebo blok výrokov (ktoré sú známe ako telo lambda). Na rozdiel od orgánov založených na výrazoch musia byť orgány založené na príkazoch umiestnené medzi otvorené ({) a zavrieť (}) zložené znaky:

(dvojitý polomer) -> Math.PI * polomer * polomer polomeru -> {návrat Math.PI * polomer * polomer; } polomer -> {System.out.println (polomer); návrat Math.PI * polomer * polomer; }

Telo lambda založené na výraze prvého príkladu nemusí byť umiestnené medzi zátvorkami. Druhý príklad prevádza telo založené na výrazoch na telo založené na príkazoch, v ktorom návrat je potrebné zadať, aby sa vrátila hodnota výrazu. Posledný príklad demonštruje viacnásobné výroky a nemožno ho vyjadriť bez zložených zátvoriek.

Telá lambda a bodkočiarky

Všimnite si neprítomnosť alebo prítomnosť bodkočiarok (;) v predchádzajúcich príkladoch. V obidvoch prípadoch nie je telo lambda ukončené bodkočiarkou, pretože lambda nie je výrok. V rámci tela lambda založeného na príkazoch však musí byť každý príkaz ukončený bodkočiarkou.

Výpis 3 predstavuje jednoduchú aplikáciu, ktorá demonštruje syntax lambda; Upozorňujeme, že tento zoznam je založený na predchádzajúcich dvoch príkladoch kódu.

Zoznam 3. LambdaDemo.java (verzia 3)

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

Zoznam 3 najskôr predstavuje BinaryCalculator a UnaryCalculator funkčné rozhrania, ktorých vypočítať () metódy vykonávajú výpočty na dvoch vstupných argumentoch alebo na jednom vstupnom argumente. Tento zoznam tiež predstavuje a LambdaDemo trieda ktorých hlavný() metóda demonštruje tieto funkčné rozhrania.

Funkčné rozhrania sú demonštrované v dokumente statický dvojitý výpočet (výpočet BinaryCalculator, dvojitý v1, dvojitý v2) a statický dvojitý výpočet (výpočet UnaryCalculator, dvojitý v) metódy. Lambdy odovzdávajú kód ako údaje k týmto metódam, ktoré sa prijímajú ako BinaryCalculator alebo UnaryCalculator inštancie.

Zostavte zoznam 3 a spustite aplikáciu. Mali by ste dodržiavať nasledujúci výstup:

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

Typy cieľov

Lambda je spojená s implicitným údajom cieľový typ, ktorý identifikuje typ objektu, na ktorý je lambda viazaná. Cieľovým typom musí byť funkčné rozhranie odvodené z kontextu, ktorý obmedzuje zobrazovanie lambdas v nasledujúcich kontextoch:

  • Deklarácia premennej
  • Postúpenie
  • Vyhlásenie o vrátení tovaru
  • Inicializátor poľa
  • Argumenty metódy alebo konštruktora
  • Lambda telo
  • Ternárny podmienený výraz
  • Obsadený výraz

Výpis 4 predstavuje aplikáciu, ktorá demonštruje tieto kontexty cieľového typu.

Zoznam 4. LambdaDemo.java (verzia 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; importovať java.nio.file.cesta; import java.nio.file.SimpleFileVisitor; import java.nio.file.attribute.BasicFileAttributes; import java.security.AccessController; import java.security.PrivilegedAction; importovať java.util.Arrays; importovať 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 {// Cieľový typ # 1: deklarácia premennej Runnable r = () -> {System.out.println ("running"); }; r.run (); // Cieľový typ # 2: priradenie r = () -> System.out.println ("bežiaci"); r.run (); // Cieľový typ č. 3: príkaz return (v 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 (súbor cesty, atribúty BasicFileAttributes) {názov cesty = file.getFileName (); pre (int i = 0; i System.out.println ("beží")). Start (); // typ cieľa # 6: lambda body (a nested lambda) Callable callable = () -> () -> System.out.println ("called"); callable.call (). Run (); // Cieľový typ č. 7: ternárny podmienený výraz boolean ascendingSort = false; Komparátor cmp; cmp = (ascendingSort)? (s1, s2) -> s1.compareTo (s2): (s1, s2) -> s2.compareTo (s1); Zoznam miest = Arrays.asList („Washington“, „Londýn“, „Rím“, „Berlín“, „Jeruzalem“, „Ottawa“, „Sydney“, „Moskva“); Collections.sort (mestá, cmp); pre (int i = 0; i <cities.size (); i ++) System.out.println (cities.get (i)); // Cieľový typ # 8: cast výraz String user = AccessController.doPrivileged ((PrivilegedAction) () -> System.getProperty ("pouzivatel.nazov ")); System.out.println (užívateľ); } statický FileFilter getFilter (reťazec ext) {návrat (názov cesty) -> názov cesty.toString (). endsWith (ext); }}
$config[zx-auto] not found$config[zx-overlay] not found