Ohjelmointi

Aloita lambda-lausekkeiden käyttö Java-käyttöjärjestelmässä

Ennen Java SE 8: ta käytettiin nimettömiä luokkia toiminnallisuuden siirtämiseksi menetelmään. Tämä käytäntö hämmentää lähdekoodia, mikä vaikeuttaa sen ymmärtämistä. Java 8 poisti tämän ongelman ottamalla käyttöön lambdas. Tämä opetusohjelma esittelee ensin lambda-kieliominaisuuden ja antaa sitten yksityiskohtaisemman johdannon toiminnalliseen ohjelmointiin lambda-lausekkeilla yhdessä kohdetyyppien kanssa. Opit myös, kuinka lambdat ovat vuorovaikutuksessa laajuuksien, paikallisten muuttujien, Tämä ja super avainsanat ja Java-poikkeukset.

Huomaa, että tämän opetusohjelman koodiesimerkit ovat yhteensopivia JDK 12: n kanssa.

Löydä tyypit itsellesi

En esitä tässä opetusohjelmassa muita kuin lambda-kielen ominaisuuksia, joista et ole aiemmin oppinut, mutta esittelen lambdoita sellaisten tyyppien kautta, joita en ole aiemmin käsitellyt tässä sarjassa. Yksi esimerkki on java.lang.Math luokassa. Esittelen nämä tyypit tulevissa Java 101 -oppaissa. Toistaiseksi suosittelen lukemaan JDK 12 API -dokumentaation saadaksesi lisätietoja niistä.

lataa Hanki koodi Lataa lähdekoodi esimerkiksi sovelluksiin tässä opetusohjelmassa. Luonut Jeff Friesen JavaWorldille.

Lambdas: Aluke

A lambda-ilmaisu (lambda) kuvaa koodilohkon (anonyymi toiminto), joka voidaan välittää rakentajille tai menetelmille myöhempää suoritusta varten. Konstruktori tai menetelmä vastaanottaa lambdan argumenttina. Harkitse seuraavaa esimerkkiä:

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

Tämä esimerkki identifioi lambda-sanoman lähettämistä vakiolähtövirtaan. Vasemmalta oikealle, () tunnistaa lambdan muodollisen parametriluettelon (esimerkissä ei ole parametreja), -> osoittaa, että ilmaisu on lambda, ja System.out.println ("Hei") on suoritettava koodi.

Lambdas yksinkertaistaa toiminnalliset rajapinnat, jotka ovat huomautettuja rajapintoja, jotka kukin ilmoittavat täsmälleen yhden abstraktin menetelmän (vaikka ne voivat myös ilmoittaa minkä tahansa oletus-, staattisen ja yksityisen menetelmän yhdistelmän). Esimerkiksi vakioluokan kirjastossa on a java.lang.Runnable käyttöliittymä yhden abstraktin kanssa void run () menetelmä. Tämän toiminnallisen käyttöliittymän vakuutus näkyy alla:

@FunctionalInterface julkinen käyttöliittymä Runnable {public abstract void run (); }

Luokan kirjasto merkitsee Ajettava kanssa @FunctionalInterface, joka on esimerkki java.lang.FunctionalInterface merkinnän tyyppi. Toiminnallinen käyttöliittymä käytetään merkitsemään niitä liitäntöjä, joita käytetään lambda-yhteyksissä.

Lambdalla ei ole nimenomaista käyttöliittymätyyppiä. Sen sijaan kääntäjä käyttää ympäröivää kontekstia päättääkseen, mikä toiminnallinen käyttöliittymä saadaan aikaan, kun lambda on määritetty - lambda on sidottu siihen käyttöliittymään. Oletetaan esimerkiksi, että määritin seuraavan koodin fragmentin, joka välittää edellisen lambdan argumenttina java.lang.Thread luokan Lanka (ajettava kohde) rakentaja:

uusi ketju (() -> System.out.println ("Hei"));

Kääntäjä määrittää, että lambda välitetään Kierre (ajettava r) koska tämä on ainoa rakentaja, joka tyydyttää lambda: Ajettava on toiminnallinen käyttöliittymä, lambdan tyhjä muodollinen parametriluettelo () Ottelut juosta()tyhjä parametriluettelo ja palautustyypit (mitätön) myös samaa mieltä. Lambda on sidottu Ajettava.

Listaus 1 näyttää lähdekoodin pienelle sovellukselle, jonka avulla voit pelata tällä esimerkillä.

Listaus 1. LambdaDemo.java (versio 1)

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

Koosta luettelo 1 (javac LambdaDemo.java) ja suorita sovellus (java LambdaDemo). Ota huomioon seuraava tulos:

Hei

Lambdas voi yksinkertaistaa merkittävästi kirjoittamasi lähdekoodin määrää ja helpottaa lähdekoodin ymmärtämistä. Esimerkiksi ilman lambdasia määrität todennäköisesti Listing 2: n tarkemman koodin, joka perustuu nimettömän luokan esiintymään, joka toteuttaa Ajettava.

Listing 2. LambdaDemo.java (versio 2)

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

Suoritettuasi tämän lähdekoodin, suorita sovellus. Löydät saman tuotoksen kuin aiemmin.

Lambdas ja Streams API

Lähdekoodin yksinkertaistamisen lisäksi lambdoilla on tärkeä rooli Java: n toiminnallisesti suuntautuneessa Streams-sovellusliittymässä. Ne kuvaavat toiminnallisuuden yksiköitä, jotka välitetään eri API-menetelmille.

Java lambdas perusteellisesti

Jos haluat käyttää lambdasia tehokkaasti, sinun on ymmärrettävä lambda-lausekkeiden syntaksit sekä kohdetyypin käsite. Sinun on myös ymmärrettävä, kuinka lambdas on vuorovaikutuksessa laajuuksien, paikallisten muuttujien ja Tämä ja super avainsanat ja poikkeukset. Käsittelen kaikki nämä aiheet seuraavissa osioissa.

Kuinka lambdas toteutetaan

Lambdat toteutetaan Java-virtuaalikoneiden näkökulmasta kutsutaan dynaamiseksi ohjeet ja java.lang.invoke API. Katso video Lambda: Peek Under Hood oppia lambda-arkkitehtuurista.

Lambda-syntaksia

Jokainen lambda noudattaa seuraavaa syntaksia:

( muodollinen-parametri-luettelo ) -> { lauseke-tai-lausunnot }

muodollinen-parametri-luettelo on pilkuilla erotettu luettelo muodollisista parametreista, joiden on vastattava toiminnallisen käyttöliittymän yhden abstraktin menetelmän parametreja ajon aikana. Jos jätät niiden tyypit pois, kääntäjä päättää nämä tyypit tilanteesta, jossa lambdaa käytetään. Harkitse seuraavia esimerkkejä:

(kaksinkertainen a, kaksinkertainen b) // nimenomaisesti määritetyt tyypit (a, b) // kääntäjän päättämät tyypit

Lambdas ja var

Java SE 11: stä alkaen voit korvata tyyppinimen nimellä var. Voit esimerkiksi määrittää (var a, var b).

Sinun on määritettävä sulkeet useille virallisille parametreille tai ei mitään. Voit kuitenkin jättää sulkumerkit pois (vaikka sinun ei tarvitse), kun määrität yhden muodollisen parametrin. (Tämä koskee vain parametrin nimeä - sulkeita vaaditaan, kun tyyppi on myös määritetty.) Harkitse seuraavia lisäesimerkkejä:

x // sulut jätetään pois yhden muodollisen parametrin vuoksi (kaksinkertainen x) // sulut vaaditaan, koska tyyppiä on myös () // sulkeita vaaditaan, kun muodollisia parametreja ei ole (x, y) // sulkeita vaaditaan useiden muodollisten parametrien vuoksi

muodollinen-parametri-luettelo seuraa a -> tunnus, jota seuraa lauseke-tai-lausunnot--lauseke tai lausekelohko (tunnetaan joko lambdan rungona). Toisin kuin lausekeperusteiset kappaleet, lausuntapohjaiset kappaleet on sijoitettava avoimen ({) ja sulje (}) aaltosulje merkit:

(kaksinkertainen säde) -> Math.PI * säde * säteen säde -> {return Math.PI * säde * säde; } säde -> {System.out.println (säde); palaa Math.PI * säde * säde; }

Ensimmäisen esimerkin ilmaisupohjaista lambda-runkoa ei tarvitse sijoittaa henkseleiden väliin. Toinen esimerkki muuntaa lausekepohjaisen rungon lausepohjaiseksi rungoksi, jossa palata on määritettävä lausekkeen arvon palauttamiseksi. Viimeinen esimerkki osoittaa useita lauseita, eikä sitä voida ilmaista ilman aaltosulkeita.

Lambda-rungot ja puolipisteet

Huomaa, että puolipisteitä ei ole tai niitä ei ole (;) edellisissä esimerkeissä. Kummassakin tapauksessa lambda-runkoa ei ole erotettu puolipisteellä, koska lambda ei ole lausunto. Lausekepohjaisessa lambda-rungossa jokainen lause on kuitenkin lopetettava puolipisteellä.

Listaus 3 esittää yksinkertaisen sovelluksen, joka osoittaa lambda-syntaksin; Huomaa, että tämä luettelo perustuu kahteen edelliseen koodiesimerkkiin.

Listaus 3. LambdaDemo.java (versio 3)

@FunctionalInterface -rajapinta BinaryCalculator {kaksinkertainen laskenta (kaksinkertainen arvo1, kaksinkertainen arvo2); } @FunctionalInterface -rajapinta UnaryCalculator {kaksoislaske (kaksinkertainen arvo); } public class LambdaDemo {public static void main (String [] args) {System.out.printf ("18 + 36,5 =% f% n", laske ((double v1, double v2) -> v1 + v2, 18, 36,5)); System.out.printf ("89 / 2,9 =% f% n", laske ((v1, v2) -> v1 / v2, 89, 2.9)); System.out.printf ("- 89 =% f% n", laske (v -> -v, 89)); System.out.printf ("18 * 18 =% f% n", laske ((kaksinkertainen v) -> v * v, 18)); } staattinen kaksinkertainen laskenta (BinaryCalculator calc, double v1, double v2) {return calc.calculate (v1, v2); } staattinen kaksinkertainen laskenta (UnaryCalculator calc, double v) {return calc.calculate (v); }}

Listaus 3 esittelee ensin Binaarinen laskin ja UnaryCalculator toiminnalliset rajapinnat, joiden laskea() menetelmillä suoritetaan laskelmat kahdelle syöteargumentille tai yhdelle syöteargumentille. Tämä luettelo esittelee myös a LambdaDemo luokka, jonka main () menetelmä osoittaa nämä toiminnalliset rajapinnat.

Toiminnalliset rajapinnat on esitetty staattinen kaksinkertainen laskenta (BinaryCalculator calc, double v1, double v2) ja staattinen kaksinkertainen laskenta (UnaryCalculator calc, double v) menetelmiä. Lambdas välittää koodin datana näihin menetelmiin, jotka vastaanotetaan muodossa Binaarinen laskin tai UnaryCalculator tapauksia.

Koosta Listaus 3 ja suorita sovellus. Ota huomioon seuraava tulos:

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

Kohdetyypit

Lambda liittyy implisiittiseen kohdetyyppi, joka tunnistaa objektityypin, johon lambda on sidottu. Kohdetyypin on oltava toiminnallinen käyttöliittymä, joka johtuu kontekstista, joka rajoittaa lambdojen esiintymisen seuraavissa yhteyksissä:

  • Muuttuva ilmoitus
  • Tehtävä
  • Palautuslauseke
  • Ryhmän alustus
  • Menetelmä tai konstruktorin argumentit
  • Lambda-runko
  • Ternäärinen ehdollinen ilmaisu
  • Valettu ilme

Listaus 4 esittää sovelluksen, joka osoittaa nämä kohdetyyppikontekstit.

Listing 4. LambdaDemo.java (versio 4)

tuo java.io.File; tuo java.io.FileFilter; tuo java.nio.file.Files; tuo java.nio.file.FileSystem; tuo java.nio.file.FileSystems; tuo java.nio.file.FileVisitor; tuo java.nio.file.FileVisitResult; tuo java.nio.file.Path; tuo java.nio.file.PathMatcher; tuo java.nio.file.Paths; tuo java.nio.file.SimpleFileVisitor; tuo java.nio.file.attribute.BasicFileAttributes; tuo java.security.AccessController; tuo java.security.PrivilegedAction; tuo java.util.Arrays; tuo java.util.Collections; tuo java.util.Comparator; tuo java.util.List; tuo java.util.concurrent.Callable; public class LambdaDemo {public static void main (String [] args) heittää poikkeuksen {// Kohdetyyppi # 1: muuttujailmoitus Runnable r = () -> {System.out.println ("käynnissä"); }; r.run (); // Kohdetyyppi # 2: tehtävä r = () -> System.out.println ("käynnissä"); r.run (); // Kohdetyyppi # 3: return-lause (tiedostossa getFilter ()) File [] files = new File ("."). ListFiles (getFilter ("txt")); for (int i = 0; i polku.toString (). endWith ("txt"), (polku) -> path.toString (). endWith ("java")}; FileVisitor-vierailija; visitor = uusi SimpleFileVisitor () { @Override public FileVisitResult visitFile (Polkutiedosto, BasicFileAttributes-attribuutit) {Polun nimi = file.getFileName (); for (int i = 0; i System.out.println ("käynnissä")). Start (); // Kohdetyyppi # 6: lambda-runko (sisäkkäinen lambda) Soitettava soitettava = () -> () -> System.out.println ("kutsuttu"); callable.call (). Run (); // Kohdetyyppi # 7: kolmikantainen ehdollinen lauseke looginen nousevaSort = false; Vertailija cmp; cmp = (ascendingSort)? (s1, s2) -> s1.compareTo (s2): (s1, s2) -> s2.compareTo (s1); Luettelo kaupungeista = Arrays.asList ("Washington", "Lontoo", "Rooma", "Berliini", "Jerusalem", "Ottawa", "Sydney", "Moskova"); Kokoelmat. Lajittelu (kaupungit, cmp); for (int i = 0; i <kaupungit.size (); i ++) System.out.println (kaupungit.get (i)); // Kohdetyyppi # 8: valettu lauseke String user = AccessController.doPrivileged ((PrivilegedAction) () -> System.getProperty ("käyttäjänimi ")); System.out.println (käyttäjä); } staattinen FileFilter getFilter (String ext) {return (polun nimi) -> pathname.toString (). endWith (ext); }}