Programování

Programování v jazyce Java s výrazy lambda

Mark Reinhold, hlavní architekt skupiny Java Platform Group ve společnosti Oracle, v technické keynote adrese pro JavaOne 2013 popsal výrazy lambda jako jediný největší upgrade programovacího modelu Java vůbec. I když existuje mnoho aplikací pro výrazy lambda, tento článek se zaměřuje na konkrétní příklad, který se často vyskytuje v matematických aplikacích; jmenovitě potřeba předat funkci algoritmu.

Jako šedovlasý geek jsem v průběhu let programoval v mnoha jazycích a od verze 1.1 jsem programoval rozsáhle v Javě. Když jsem začal pracovat s počítači, téměř nikdo nevystudoval informatiku. Počítačoví profesionálové pocházeli většinou z jiných oborů, jako je elektrotechnika, fyzika, obchod a matematika. V mém vlastním dřívějším životě jsem byl matematik, a proto by nemělo být překvapením, že můj původní pohled na počítač byl pohled na obrovskou programovatelnou kalkulačku. V průběhu let jsem značně rozšířil svůj pohled na počítače, ale přesto vítám příležitost pracovat na aplikacích, které zahrnují určitý aspekt matematiky.

Mnoho aplikací v matematice vyžaduje, aby byla funkce předána jako parametr algoritmu. Příklady z vysokoškolské algebry a základního počtu zahrnují řešení rovnice nebo výpočet integrálu funkce. Již více než 15 let je Java mým programovacím jazykem volby pro většinu aplikací, ale byl to první jazyk, který jsem často používal a který mi nedovolil předat funkci (technicky ukazatel nebo odkaz na funkci) jako parametr jednoduchým a přímým způsobem. Tento nedostatek se s blížícím se vydáním Java 8 změní.

Síla výrazů lambda přesahuje mnohem víc než jeden případ použití, ale studium různých implementací stejného příkladu by vám mělo přinést solidní představu o tom, jak budou lambdy pro vaše programy Java prospěšné. V tomto článku použiji běžný příklad k popisu problému, poté poskytnu řešení napsaná v C ++, Java před výrazy lambda a Java s výrazy lambda. Uvědomte si, že pro pochopení a zhodnocení hlavních bodů tohoto článku není nutné silné zázemí v matematice.

Učení o lambdách

Výrazy lambda, známé také jako uzávěry, funkční literály nebo jednoduše lambdy, popisují sadu funkcí definovaných v JSR (Java Specification Request) 335. Méně formální / čitelnější úvod do výrazů lambda je uveden v části nejnovější verze Výukový program Java a v několika článcích Briana Goetze „State of the lambda“ a „State of the lambda: Libraries edition“. Tyto zdroje popisují syntaxi výrazů lambda a poskytují příklady případů použití, kde jsou výrazy lambda použitelné. Další informace o výrazech lambda v Javě 8 najdete na technické hlavní adrese Mark Reinhold pro JavaOne 2013.

Lambda výrazy v matematickém příkladu

Příklad použitý v tomto článku je Simpsonovo pravidlo ze základního počtu. Simpsonovo pravidlo, přesněji řečeno složené Simpsonovo pravidlo, je numerická integrační technika k přiblížení určitého integrálu. Nedělejte si starosti, pokud jste obeznámeni s konceptem a určitý integrál; to, co opravdu potřebujete pochopit, je, že Simpsonovo pravidlo je algoritmus, který počítá skutečné číslo na základě čtyř parametrů:

  • Funkce, kterou chceme integrovat.
  • Dvě reálná čísla A a b které představují koncové body intervalu [a, b] na řádku reálného čísla. (Upozorňujeme, že výše uvedená funkce by měla být v tomto intervalu spojitá.)
  • Sudé celé číslo n který určuje počet podintervalů. Při implementaci Simpsonova pravidla rozdělíme interval [a, b] do n podintervaly.

Abychom prezentaci zjednodušili, zaměřme se na programovací rozhraní a ne na podrobnosti implementace. (Po pravdě řečeno, doufám, že tento přístup nám umožní obejít argumenty o nejlepším nebo nejúčinnějším způsobu implementace Simpsonova pravidla, které není předmětem tohoto článku.) Použijeme typ dvojnásobek pro parametry A a ba použijeme typ int pro parametr n. Funkce, která má být integrována, bude mít jediný parametr typu dvojnásobek a vrátí hodnotu typu dvojnásobek.

Stáhnout Stáhněte si příklad zdrojového kódu C ++ pro tento článek. Vytvořil John I. Moore pro JavaWorld

Parametry funkce v C ++

Abychom poskytli základ pro srovnání, pojďme začít se specifikací C ++. Při předávání funkce jako parametru v C ++ obvykle dávám přednost zadání podpisu parametru funkce pomocí a typedef. Výpis 1 ukazuje soubor záhlaví C ++ s názvem simpson.h který specifikuje jak typedef pro parametr funkce a programovací rozhraní pro pojmenovanou funkci C ++ integrovat. Tělo funkce pro integrovat je obsažen v souboru zdrojového kódu C ++ s názvem simpson.cpp (není zobrazeno) a poskytuje implementaci Simpsonova pravidla.

Výpis 1. Hlavičkový soubor C ++ pro Simpsonovo pravidlo

 #if! defined (SIMPSON_H) #define SIMPSON_H #include using namespace std; typedef double DoubleFunction (double x); double integrate (DoubleFunction f, double a, double b, int n) throw (invalid_argument); #endif 

Povolání integrovat je v C ++ přímočarý. Jako jednoduchý příklad předpokládejme, že jste chtěli použít Simpsonovo pravidlo k přiblížení integrálu sinus funkce od 0 na π (PI) použitím 30 podintervaly. (Kdokoli, kdo dokončil Calculus I, by měl být schopen vypočítat odpověď přesně bez pomoci kalkulačky, což z něj dělá dobrý testovací případ pro integrovat funkce.) Za předpokladu, že jste měli zahrnuta správné hlavičkové soubory jako a „simpson.h“, budete moci volat funkci integrovat jak je uvedeno v seznamu 2.

Výpis 2. C ++ volání funkce integrovat

 dvojitý výsledek = integrovat (sin, 0, M_PI, 30); 

To je vše. V C ++ předáte sinus fungují stejně snadno, jako předáte další tři parametry.

Další příklad

Místo Simpsonova pravidla jsem mohl stejně snadno použít metodu bisekce (aka Bisection Algorithm) pro řešení rovnice tvaru f (x) = 0. Ve skutečnosti zdrojový kód tohoto článku obsahuje jednoduché implementace Simpsonova pravidla a metody bisekce.

Stáhnout Stáhněte si příklady zdrojových kódů Java pro tento článek. Vytvořil John I. Moore pro JavaWorld

Java bez výrazů lambda

Nyní se podívejme na to, jak může být Simpsonovo pravidlo specifikováno v Javě. Bez ohledu na to, zda používáme výrazy lambda, používáme místo C ++ rozhraní Java zobrazené ve výpisu 3 typedef určit podpis funkčního parametru.

Výpis 3. Rozhraní Java pro funkční parametr

 veřejné rozhraní DoubleFunction {public double f (double x); } 

Abychom implementovali Simpsonovo pravidlo v Javě, vytvořili jsme třídu s názvem Simpson který obsahuje metodu, integrovat, se čtyřmi parametry podobnými tomu, co jsme udělali v C ++. Stejně jako u mnoha samostatných matematických metod (viz například java.lang.Math), uděláme integrovat statická metoda. Metoda integrovat je specifikováno následovně:

Výpis 4. Podpis Java pro integraci metody do třídy Simpson

 public static double integrate (DoubleFunction df, double a, double b, int n) 

Všechno, co jsme dosud v Javě udělali, je nezávislé na tom, zda budeme používat výrazy lambda. Primární rozdíl s výrazy lambda je v tom, jak předáváme parametry (konkrétněji, jak předáváme parametr funkce) ve volání metody integrovat. Nejprve ilustruji, jak by to bylo provedeno ve verzích Java před verzí 8; tj. bez výrazů lambda. Stejně jako v příkladu C ++ předpokládejme, že chceme aproximovat integrál sinus funkce od 0 na π (PI) použitím 30 podintervaly.

Použití vzoru adaptéru pro sinusovou funkci

V Javě máme implementaci sinus funkce dostupná v java.lang.Math, ale s verzemi Javy staršími než Java 8 neexistuje jednoduchý a přímý způsob, jak to předat sinus funkce k metodě integrovat ve třídě Simpson. Jedním z přístupů je použití vzoru adaptéru. V tomto případě bychom napsali jednoduchou třídu adaptéru, která implementuje DoubleFunction rozhraní a přizpůsobuje jej volání sinus funkce, jak je uvedeno v Seznamu 5.

Výpis 5. Třída adaptéru pro metodu Math.sin

 import com.softmoore.math.DoubleFunction; veřejná třída DoubleFunctionSineAdapter implementuje DoubleFunction {public double f (double x) {return Math.sin (x); }} 

Pomocí této třídy adaptéru můžeme nyní volat integrovat metoda třídy Simpson jak je uvedeno v seznamu 6.

Výpis 6. Použití třídy adaptéru k volání metody Simpson.integrate

 DoubleFunctionSineAdapter sine = nový DoubleFunctionSineAdapter (); dvojitý výsledek = Simpson.integrate (sine, 0, Math.PI, 30); 

Zastavme se na okamžik a porovnejme, co bylo potřeba k uskutečnění hovoru integrovat v C ++ oproti tomu, co bylo požadováno v dřívějších verzích Javy. S C ++ jsme jednoduše volali integrovat, předáním čtyř parametrů. S Javou jsme museli vytvořit novou třídu adaptéru a poté vytvořit instanci této třídy, abychom mohli uskutečnit hovor. Pokud bychom chtěli integrovat několik funkcí, museli bychom pro každou z nich napsat třídu adaptéru.

Mohli bychom zkrátit kód potřebný k volání integrovat mírně ze dvou příkazů Java na jeden vytvořením nové instance třídy adaptéru v rámci volání integrovat. Použití anonymní třídy namísto vytvoření samostatné třídy adaptéru by bylo dalším způsobem, jak mírně snížit celkové úsilí, jak je uvedeno v seznamu 7.

Výpis 7. Použití anonymní třídy k volání metody Simpson.integrate

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

Bez výrazů lambda je to, co vidíte ve Výpisu 7, nejmenší množství kódu, který byste mohli napsat v Javě pro volání integrovat metoda, ale je to stále mnohem těžkopádnější, než jaké bylo požadováno pro C ++. Také nejsem spokojený s používáním anonymních tříd, i když jsem je v minulosti hodně používal. Nelíbí se mi syntaxe a vždy jsem ji považoval za neohrabaný, ale nezbytný hack v jazyce Java.

Java s výrazy lambda a funkčními rozhraními

Nyní se podívejme na to, jak bychom mohli pomocí výrazů lambda v jazyce Java 8 zjednodušit volání integrovat v Javě. Protože rozhraní DoubleFunction vyžaduje implementaci pouze jedné metody, je kandidátem na výrazy lambda. Pokud předem víme, že budeme používat výrazy lambda, můžeme anotovat rozhraní pomocí @Funkčnírozhraní, nová anotace pro Java 8, která říká, že máme funkční rozhraní. Všimněte si, že tato anotace není nutná, ale poskytuje nám další kontrolu, zda je vše konzistentní, podobně jako @ Přepis anotace v dřívějších verzích Java.

Syntaxe výrazu lambda je seznam argumentů uzavřený v závorkách, token šipky (->) a funkční tělo. Tělo může být buď blok příkazů (uzavřený ve složených závorkách), nebo jeden výraz. Výpis 8 ukazuje výraz lambda, který implementuje rozhraní DoubleFunction a poté je předán metodě integrovat.

Výpis 8. Použití výrazu lambda k volání metody Simpson.integrate

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

Všimněte si, že jsme nemuseli psát třídu adaptéru nebo vytvářet instanci anonymní třídy. Všimněte si také, že jsme mohli napsat výše v jediném příkazu nahrazením samotného výrazu lambda, (double x) -> Math.sin (x), pro parametr sinus ve druhém prohlášení výše, což vylučuje první prohlášení. Nyní se dostáváme mnohem blíže k jednoduché syntaxi, kterou jsme měli v C ++. Ale počkej! Je toho víc!

Název funkčního rozhraní není součástí výrazu lambda, ale lze jej odvodit na základě kontextu. Typ dvojnásobek pro parametr výrazu lambda lze odvodit také z kontextu. Nakonec, pokud je ve výrazu lambda pouze jeden parametr, můžeme závorky vynechat. Můžeme tedy zkrátit metodu volání kódu integrovat na jeden řádek kódu, jak je uvedeno v Seznamu 9.

Výpis 9. Alternativní formát pro výraz lambda ve volání Simpson.integrate

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

Ale počkej! Je toho ještě víc!

Odkazy na metody v prostředí Java 8

Další související funkcí v Javě 8 je něco, co se nazývá a odkaz na metodu, což nám umožňuje odkazovat na existující metodu podle názvu. Místo výrazů lambda lze použít odkazy na metody, pokud splňují požadavky funkčního rozhraní. Jak je popsáno v prostředcích, existuje několik různých druhů odkazů na metody, každý s mírně odlišnou syntaxí. Pro statické metody je syntaxe Classname :: methodName. Proto můžeme pomocí odkazu na metodu zavolat integrovat metoda v Javě tak jednoduše, jak jsme mohli v C ++. Porovnejte volání Java 8 zobrazené v seznamu 10 níže s původním hovorem C ++ zobrazeným v seznamu 2 výše.

Výpis 10. Použití odkazu na metodu k volání Simpson.integrate

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