Programovanie

Programovanie v jazyku Java s výrazmi lambda

Mark Reinhold, hlavný architekt skupiny Java Platform Group v spoločnosti Oracle, v technickej príhovore pre program JavaOne 2013 opísal výrazy lambda ako jediný najväčší upgrade programovacieho modelu Java. vôbec. Aj keď existuje veľa aplikácií pre výrazy lambda, tento článok sa zameriava na konkrétny príklad, ktorý sa často vyskytuje v matematických aplikáciách; a to potreba odovzdať funkciu algoritmu.

Ako šedovlasý geek som v priebehu rokov programoval v mnohých jazykoch a od verzie 1.1 som programoval vo veľkej miere v Jave. Keď som začal pracovať s počítačmi, takmer nikto nemal diplom z informatiky. Počítačoví profesionáli pochádzali väčšinou z iných odborov ako elektrotechnika, fyzika, obchod a matematika. V mojom bývalom živote som bol matematik, a preto by ma nemalo prekvapovať, že môj pôvodný pohľad na počítač bol obrovský programovateľný kalkulátor. V priebehu rokov som si výrazne rozšíril pohľad na počítače, ale stále vítam príležitosť pracovať na aplikáciách, ktoré zahŕňajú nejaký aspekt matematiky.

Mnoho aplikácií v matematike vyžaduje, aby sa funkcia odovzdala ako parameter do algoritmu. Príklady z univerzitnej algebry a základného počtu zahŕňajú riešenie rovnice alebo výpočet integrálu funkcie. Už viac ako 15 rokov je Java mojím programovacím jazykom pre väčšinu aplikácií, ale bol to prvý jazyk, ktorý som často používal a ktorý mi nedovolil odovzdať funkciu (technicky ukazovateľ alebo odkaz na funkciu) ako parametrom jednoduchým a priamym spôsobom. Tento nedostatok sa s blížiacim sa vydaním Java 8 zmení.

Sila výrazov lambda presahuje rámec jedného prípadu použitia, ale štúdium rôznych implementácií toho istého príkladu by vám malo poskytnúť dobrý prehľad o tom, aký prínos bude mať lambdas pre vaše programy Java. V tomto článku použijem bežný príklad na opísanie problému, potom poskytnem riešenia napísané v jazykoch C ++, Java pred výrazmi lambda a Java s výrazmi lambda. Upozorňujeme, že na pochopenie a ocenenie hlavných bodov tohto článku nie je potrebné silné základy v matematike.

Dozvedieť sa o lambdách

Výrazy lambda, známe tiež ako uzávery, funkčné literály alebo jednoducho lambdy, popisujú množinu funkcií definovaných v požiadavke JSR (Java Specification Request) 335. Menej formálne a čitateľnejšie úvodné informácie o výrazoch lambda sú uvedené v časti najnovšej verzie Výukový program Java a v niekoľkých článkoch Briana Goetza „State of the lambda“ a „State of the lambda: Libraries edition“. Tieto zdroje popisujú syntaxi výrazov lambda a poskytujú príklady prípadov použitia, kde sú výrazy lambda použiteľné. Ak sa chcete dozvedieť viac informácií o výrazoch lambda v prostredí Java 8, pozrite si technickú kľúčovú adresu Mark Reinhold pre JavaOne 2013.

Lambda výrazy na matematickom príklade

Príkladom použitým v tomto článku je Simpsonovo pravidlo zo základného počtu. Simpsonovo pravidlo alebo presnejšie povedané Simpsonovo pravidlo je numerická integračná technika na priblíženie určitého integrálu. Nerobte si starosti, ak vám nie je známy koncept a určitý integrál; to, čo skutočne potrebujete pochopiť, je, že Simpsonovo pravidlo je algoritmus, ktorý počíta skutočné číslo na základe štyroch parametrov:

  • Funkcia, ktorú chceme integrovať.
  • Dve reálne čísla a a b ktoré predstavujú koncové body intervalu [a, b] na riadku skutocneho cisla. (Upozorňujeme, že vyššie uvedená funkcia by mala byť v tomto intervale nepretržitá.)
  • Párne celé číslo n ktorá určuje počet podinterválov. Pri implementácii Simpsonovho pravidla rozdeľujeme interval [a, b] do n podintervaly.

Pre zjednodušenie prezentácie sa zamerajme na programovacie rozhranie a nie na podrobnosti implementácie. (Po pravde povedané, dúfam, že tento prístup nám umožní obísť argumenty o najlepšom alebo najefektívnejšom spôsobe implementácie Simpsonovho pravidla, na ktoré sa tento článok nezameriava.) Použijeme typ dvojitý pre parametre a a b, a použijeme typ int pre parameter n. Integrovaná funkcia bude mať jediný parameter typu dvojitý a vráti hodnotu typu dvojitý.

Stiahnutie Stiahnite si príklad zdrojového kódu C ++ pre tento článok. Vytvoril John I. Moore pre JavaWorld

Funkčné parametre v C ++

Aby sme poskytli základ pre porovnanie, začnime so špecifikáciou C ++. Pri odovzdávaní funkcie ako parametra v C ++ zvyčajne uprednostňujem špecifikáciu podpisu parametra funkcie pomocou a typedef. Výpis 1 zobrazuje súbor hlavičky C ++ s názvom simpson.h ktorá špecifikuje obidve typedef pre parameter funkcie a programovacie rozhranie pre pomenovanú funkciu C ++ integrovať. Funkčný orgán pre integrovať je obsiahnutý v súbore zdrojového kódu C ++ s názvom simpson.cpp (nezobrazené) a poskytuje implementáciu Simpsonovho pravidla.

Zoznam 1. Hlavičkový súbor C ++ pre Simpsonovo pravidlo

 #if! definované (SIMPSON_H) # definovať SIMPSON_H # zahrnúť pomocou štandardného priestoru mien; typedef double DoubleFunction (double x); double integrate (DoubleFunction f, double a, double b, int n) throw (invalid_argument); #koniec Ak 

Telefonovanie integrovať je v C ++ jednoduchý. Ako jednoduchý príklad predpokladajme, že ste chceli použiť Simpsonovo pravidlo na aproximáciu integrálu sínus funkcia z 0 do π (PI) použitím 30 podintervaly. (Každý, kto dokončil program Calculus I, by mal byť schopný vypočítať odpoveď presne bez pomoci kalkulačky, čo z neho robí dobrý testovací prípad pre integrovať funkcia.) Za predpokladu, že ste mali v cene správne hlavičkové súbory ako a „simpson.h“, boli by ste schopní zavolať funkciu integrovať ako je uvedené v zozname 2.

Výpis 2. C ++ volanie funkcie integrovať

 dvojitý výsledok = integrovať (sin, 0, M_PI, 30); 

To je všetko. V C ++ odovzdáte znak sínus fungujú tak ľahko, ako miniete ďalšie tri parametre.

Ďalší príklad

Namiesto Simpsonovho pravidla som mohol rovnako ľahko použiť metódu bisekcie (alias Bisection Algorithm) na riešenie rovnice tvaru f (x) = 0. Zdrojový kód tohto článku v skutočnosti obsahuje jednoduché implementácie Simpsonovho pravidla aj metódy bisekcie.

Stiahnutie Stiahnite si príklady zdrojových kódov Java pre tento článok. Vytvoril John I. Moore pre JavaWorld

Java bez výrazov lambda

Poďme sa teraz pozrieť na to, ako môže byť Simpsonovo pravidlo špecifikované v Jave. Bez ohľadu na to, či používame alebo nepoužívame výrazy lambda, namiesto C ++ používame rozhranie Java zobrazené v zozname 3. typedef na určenie podpisu parametra funkcie.

Zoznam 3. Rozhranie Java pre parameter funkcie

 verejné rozhranie DoubleFunction {public double f (double x); } 

Na implementáciu Simpsonovho pravidla v Jave vytvoríme triedu s názvom Simpson ktorý obsahuje metódu, integrovaťso štyrmi parametrami podobnými tým, ktoré sme robili v C ++. Rovnako ako pri mnohých samostatných matematických metódach (pozri napríklad java.lang.Math), urobíme integrovať statická metóda. Metóda integrovať je špecifikované takto:

Výpis 4. Podpis Java pre integráciu metódy v triede Simpson

 verejné statické dvojité integrovanie (DoubleFunction df, double a, double b, int n) 

Všetko, čo sme doteraz v Jave urobili, je nezávislé od toho, či budeme alebo nebudeme používať výrazy lambda. Primárny rozdiel s výrazmi lambda je v tom, ako odovzdávame parametre (konkrétnejšie ako odovzdávame funkčný parameter) vo výzve metódy integrovať. Najprv ukážem, ako by sa to malo robiť vo verziách Java pred verziou 8; teda bez výrazov lambda. Rovnako ako v príklade C ++, predpokladajme, že chceme aproximovať integrál funkcie sínus funkcia z 0 do π (PI) použitím 30 podintervaly.

Použitie vzoru adaptéra pre funkciu sínus

V Jave máme implementáciu sínus funkcia dostupná v java.lang.Math, ale s verziami Java pred Java 8 neexistuje jednoduchý, priamy spôsob, ako to odovzdať sínus funkcie k metóde integrovať v triede Simpson. Jedným z prístupov je použitie vzoru adaptéra. V tomto prípade by sme napísali jednoduchú triedu adaptéra, ktorá implementuje DoubleFunction rozhranie a prispôsobuje ho volaniu sínus ako je uvedené v zozname 5.

Zoznam 5. Trieda adaptéra pre metódu Math.sin

 import com.softmoore.math.DoubleFunction; verejná trieda DoubleFunctionSineAdapter implementuje DoubleFunction {public double f (double x) {return Math.sin (x); }} 

Pomocou tejto triedy adaptéra môžeme teraz volať integrovať metóda triedy Simpson ako je uvedené v zozname 6.

Zoznam 6. Použitie triedy adaptéra na volanie metódy Simpson.integrate

 DoubleFunctionSineAdapter sine = nový DoubleFunctionSineAdapter (); dvojitý výsledok = Simpson.integrate (sínus, 0, Math.PI, 30); 

Zastavme sa na chvíľu a porovnajme, čo bolo potrebné na uskutočnenie hovoru integrovať v C ++ oproti požiadavkám v starších verziách Java. S C ++ sme jednoducho volali integrovať, ktorý predáva štyri parametre. S programom Java sme museli uskutočniť novú triedu adaptéra a potom túto triedu vytvoriť, aby sme mohli uskutočniť hovor. Ak by sme chceli integrovať niekoľko funkcií, museli by sme pre každú z nich napísať triedu adaptéra.

Mohli by sme skrátiť kód potrebný na volanie integrovať mierne z dvoch príkazov Java na jeden vytvorením novej inštancie triedy adaptéra vo výzve integrovať. Používanie anonymnej triedy namiesto vytvárania samostatnej triedy adaptéra by bolo ďalším spôsobom, ako mierne znížiť celkové úsilie, ako je uvedené v zozname 7.

Zoznam 7. Používanie anonymnej triedy na volanie metódy Simpson.integrate

 DoubleFunction sineAdapter = new DoubleFunction () {public double f (double x) {return Math.sin (x); }}; dvojitý výsledok = Simpson.integrate (sineAdapter, 0, Math.PI, 30); 

Bez výrazov lambda je to, čo vidíte v zozname 7, najmenšie množstvo kódu, ktorý by ste mohli napísať v Jave, aby ste zavolali integrovať metóda, ale stále je to oveľa ťažkopádnejšie, ako sa vyžadovalo pre C ++. Tiež nie som tak spokojný s používaním anonymných tried, aj keď som ich v minulosti veľa používal. Nepáči sa mi syntax a vždy som ju považoval za neohrabaný, ale nevyhnutný hack v jazyku Java.

Java s výrazmi lambda a funkčnými rozhraniami

Teraz sa pozrime na to, ako by sme mohli v Java 8 použiť výrazy lambda na zjednodušenie volania na integrovať v Jave. Pretože rozhranie DoubleFunction vyžaduje implementáciu iba jedinej metódy, je kandidátom na výrazy lambda. Ak vopred vieme, že budeme používať výrazy lambda, môžeme rozhranie anotovať pomocou @Funkčné rozhranie, nová anotácia pre Java 8, ktorá hovorí, že máme funkčné rozhranie. Upozorňujeme, že táto anotácia nie je povinná, ale poskytuje nám ďalšiu kontrolu, či je všetko konzistentné, podobne ako v prípade @ Override anotácia v starších verziách Java.

Syntax výrazu lambda je zoznam argumentov uzavretý v zátvorkách, šípkový token (->) a funkčný orgán. Text môže byť buď blok príkazov (uzavretý v zložených zátvorkách) alebo jeden výraz. Výpis 8 zobrazuje výraz lambda, ktorý implementuje rozhranie DoubleFunction a potom sa odovzdá metóde integrovať.

Zoznam 8. Použitie výrazu lambda na volanie metódy Simpson.integrate

 DoubleFunction sine = (double x) -> Math.sin (x); dvojitý výsledok = Simpson.integrate (sínus, 0, Math.PI, 30); 

Všimnite si, že sme nemuseli písať triedu adaptéra alebo vytvárať inštanciu anonymnej triedy. Všimnite si tiež, že sme to mohli napísať do jedného príkazu nahradením samotného výrazu lambda, (double x) -> Math.sin (x), pre parameter sínus v druhom výroku vyššie, čím sa prvý výrok vylučuje. Teraz sa dostávame oveľa bližšie k jednoduchej syntaxi, ktorú sme mali v C ++. Ale počkaj! Je toho viac!

Názov funkčného rozhrania nie je súčasťou výrazu lambda, ale dá sa odvodiť na základe kontextu. Typ dvojitý pre parameter výrazu lambda možno odvodiť aj z kontextu. Nakoniec, ak je vo výraze lambda iba jeden parameter, môžeme zátvorky vynechať. Takto môžeme skrátiť metódu kódu na volanie integrovať na jeden riadok kódu, ako je uvedené v zozname 9.

Výpis 9. Alternatívny formát pre výraz lambda vo výzve na Simpson.integrate

 dvojitý výsledok = Simpson.integrate (x -> Math.sin (x), 0, Math.PI, 30); 

Ale počkaj! Je toho ešte viac!

Odkazy na metódy v prostredí Java 8

Ďalšou súvisiacou funkciou v prostredí Java 8 je niečo, čo sa nazýva a odkaz na metódu, ktorý nám umožňuje odkázať na existujúcu metódu podľa názvu. Namiesto výrazov lambda je možné použiť odkazy na metódy, pokiaľ vyhovujú požiadavkám funkčného rozhrania. Ako je opísané v zdrojoch, existuje niekoľko rôznych druhov odkazov na metódy, každý s mierne odlišnou syntaxou. Pre statické metódy je to syntax Classname :: methodName. Preto pomocou odkazu na metódu môžeme nazvať integrovať metódu v Jave tak jednoducho, ako by sme to dokázali v C ++. Porovnajte volanie Java 8 zobrazené v zozname 10 nižšie s pôvodným volaním C ++ uvedeným v zozname 2 vyššie.

Zoznam 10. Použitie odkazu na metódu na volanie Simpson.integrate

 dvojitý výsledok = Simpson.integrate (Math :: sin, 0, Math.PI, 30); 
$config[zx-auto] not found$config[zx-overlay] not found