Hlavní navigace

Novinky v JDK 7 aneb mírný pokrok v mezích zákona – projekt Lambda

2. 12. 2010
Doba čtení: 18 minut

Sdílet

Ve čtvrté části seriálu o nových vlastnostech jazyka Java si řekneme o projektu Lambda, který sice není – a po přijetí plánu „B“ ani nebude – oficiální součástí JDK 7, ovšem tento projekt je možné poměrně snadným způsobem doinstalovat a odzkoušet i na současné verzi JDK 7 nebo OpenJDK 7.

Obsah

1. JDK 7 a projekt Lambda

2. Aplikace a utility nutné pro instalaci projektu Lambda

3. Instalace projektu Lambda

4. Postupný vývoj syntaxe a sémantiky použité pro zápis anonymních funkcí

5. Návrh Marka Reinholda (Sun, Oracle) – slavný „straw-man proposal“

6. Problematické části návrhu Marka Reinholda aneb lze vůbec vhodně zařadit lambda výrazy do Javy?

7. Současná podoba implementace anonymních funkcí

8. SAM a jejich inicializace

9. Odkazy na Internetu

1. JDK 7 a projekt Lambda

V závěrečné kapitole předchozí části miniseriálu o novinkách, s nimiž se můžeme setkat v JDK 7 nebo které jsou teprve připravovány v rámci budoucích verzí JDK 8 a JDK 9, jsme se taktéž zmínili o projektu Lambda. Hlavním cílem tohoto projektu je rozšíření syntaxe a sémantiky programovacího jazyka Java o takzvané anonymní funkce a s nimi souvisejícími uzávěry (closures). Kromě těchto dvou zejména sémantických změn se do jazyka zavedl nový datový typ „funkce“, který rozšiřuje stávající repertoár primitivních datových typů a referencí na objekty. Spolu s těmito změnami se zjednodušila práce s anonymními třídami implementujícími pouze jednu metodu (třídy s těmito vlastnostmi se nazývají Single Abstract Method neboli SAM). Projekt Lambda sice nakonec nebude součástí JDK 7, ovšem vzhledem k tomu, že anonymní funkce a uzávěry představují poměrně revoluční změnu v jazyku Java, ukážeme si v následujících kapitolách, jakým způsobem je možné tento projekt využít již dnes a jaké možnosti programátorům nabízí.

Zatímco prakticky všechny syntaktické novinky představené v předchozích třech částech tohoto seriálu byly vlastně pouze evoluční (syntaktický cukr, popř. rozšíření některé již existující jazykové konstrukce), je zavedení anonymních funkcí a uzávěrů spíše revoluce, alespoň na poli Javy. Trošku se tím potvrzuje známé motto, že všechny jazyky postupným vývojem a vylepšováním časem dospějí k LISPu :-), i když LISP samozřejmě není jediným jazykem, v němž se anonymní funkce a uzávěry používají (byl však jazykem prvním, který tyto vlastnosti podporoval). Anonymní funkce, též známé jako lambda výrazy, jsou však v Javě zvláštní tím, že jsou staticky typované (specifikuje se jak typ lambda výrazu, tak i typy jeho parametrů – vše v rámci hlavičky metody uvedené v abstraktní třídě nebo rozhraní), zatímco klasické lambda výrazy známe spíše z dynamicky typovaných programovacích jazyků. Jak uvidíme v dalším textu, mají typované lambda výrazy oproti svým obecným protějškům (kde se kontroly typů provádí až v době běhu programu), některé výhody, ale taktéž zápory.

2. Aplikace a utility nutné pro instalaci projektu Lambda

Projekt Lambda sice není a ani nebude součástí oficiálního vydání JDK 7, ovšem poměrně jednoduchým způsobem je možné si tento projekt nainstalovat současně s některou z aktuálních verzí JDK 7 nabízených firmou Oracle na této stránce: http://dlc.sun­.com.edgesuite­.net/jdk7/bina­ries/index.html. Pokud nechcete ve vašem systému používat binární uzavřené aplikace, je samozřejmě možné si nainstalovat nebo přeložit OpenJDK 7, viz též návod uvedený na stránce http://hg.open­jdk.java.net/jdk7/bu­ild/raw-file/tip/README-builds.html. V dalším textu si ukážeme postup instalace projektu Lambda, který je možné použít jak společně s JDK 7, tak i s předem přeloženým OpenJDK 7. Pro úspěšný překlad a následné využití projektu Lambda je nutné, aby ve vašem systému byly nainstalovány následující aplikace a utility:

  1. JDK 6, OpenJDK 6 nebo IcedTea 6 – použito společně s utilitou Ant při překladu projektu Lambda (OpenJDK 6 resp. přesněji řečeno IcedTea 6 se nachází v repositářích prakticky všech významných Linuxových distribucí, takže by s její instalací neměl být problém).
  2. JDK 7 nebo OpenJDK 7 (postačuje lokální instalace, získaná například rozbalením archivu staženého ze stránek společnosti Oracle) – tato verze JDK je používána při překladu Javovských programů využívajících projekt Lambda
  3. Ant ve verzi alespoň 1.7.0
  4. Mercurial – tento nástroj pro správu verzí (podobný GIT, ale na rozdíl od něj napsaný v Pythonu) je použit pro stažení posledního snapshotu projektu Lambda. Ostatně všechny zdrojové kódy OpenJDK, IcedTea i podpůrných nástrojů jsou dostupné právě v úložištích spravovaných pomocí tohoto nástroje (z příkazové řádky se volá příkazem hg, a asi není těžké uhodnout, proč byl zvolen zrovna tento název).

3. Instalace projektu Lambda

Pokud již máte v systému nainstalovány všechny nástroje zmíněné v předchozí kapitole, je možné provést stažení zdrojových kódů projektu Lambda a k němu příslušných nástrojů (například se jedná o poměrně rozsáhlou sadu testů). To lze provést následujícím příkazem:

hg clone http://hg.openjdk.java.net/lambda/lambda/langtools

Popř. lze zdrojové kódy ručně stáhnout ze stránky http://hg.open­jdk.java.net/lam­bda/lambda/lan­gtools a poté rozbalit vhodným nástrojem (unzip, tar), v závislosti na tom, který typ archivu byl stažen. Po stažení zdrojových kódů z úložiště by se měl v aktuálním adresáři objevit podadresář langtools. Pokud se skutečně tento adresář objevil i s příslušnými soubory a podadresáři, lze pokračovat v překladu. Nejprve se musíme dostat do adresáře obsahujícího build.xml:

cd langtools/make

… a v tomto adresáři spustit příkaz ant, kterému se ve dvou parametrech boot.java.home a target.java.home předá cesta k nainstalované JDK 6/OpenJDK 6/IcedTea 6 a cesta k (většinou pouze lokální) instalaci JDK 7/OpenJDK 7. Povšimněte si, že cesta k lokální instalaci JDK 7 je v níže uvedeném příkladu zadána absolutně. I když by teoreticky mělo postačovat zadat pouze ~/jdk7/jdk1.7.0, nebudou v tomto případě spouštěcí skripty projektu Lambda pracovat korektně (tato chyba se projevila ještě ve včerejším snapshotu, i když je možné, že již bude v době vydání tohoto článku opravena). Spusťme tedy vlastní překlad:

ant -Dboot.java.home=/usr/lib/jvm/java-6-openjdk/ -Dtarget.java.home=/home/pavel/jdk7/jdk1.7.0/ build-all-tools

Překlad by měl být na současných počítačích dokončen maximálně za několik desítek sekund. Čas v následujícím výpisu 2:13 platí pro dnes již poněkud dýchavičný počítač s mikroprocesorem Athlon „Classic“ @800 MHz s 512 MB RAM (důležitý však není čas překladu, ale hlášení BUILD SUCCESSFUL :-):

Buildfile: build.xml

-def-pcompile:
    [mkdir] Created dir: /home/pavel/jdk7/langtools/build/toolclasses
    [javac] Compiling 2 source files to /home/pavel/jdk7/langtools/build/toolclasses

-def-build-classes:

-def-build-bootstrap-classes:

-def-build-jar:

-def-build-bootstrap-jar:

-def-check:

-check-boot.java.home:

-def-build-tool:

-def-build-bootstrap-tool:

build-bootstrap-javac:
    [mkdir] Created dir: /home/pavel/jdk7/langtools/build/bootstrap/gensrc
    [mkdir] Created dir: /home/pavel/jdk7/langtools/build/bootstrap/classes
 [pcompile] Generating 7 resource files to /home/pavel/jdk7/langtools/build/bootstrap/gensrc
     [copy] Copying 1 file to /home/pavel/jdk7/langtools/build/bootstrap/gensrc
 [pcompile] Generating 1 resource files to /home/pavel/jdk7/langtools/build/bootstrap/gensrc
    [javac] Compiling 297 source files to /home/pavel/jdk7/langtools/build/bootstrap/classes
     [copy] Copying 3 files to /home/pavel/jdk7/langtools/build/bootstrap/classes
    [mkdir] Created dir: /home/pavel/jdk7/langtools/build/bootstrap/lib
      [jar] Building jar: /home/pavel/jdk7/langtools/build/bootstrap/lib/javac.jar
    [mkdir] Created dir: /home/pavel/jdk7/langtools/build/bootstrap/bin
     [copy] Copying 1 file to /home/pavel/jdk7/langtools/build/bootstrap/bin

-def-genstubs:

-create-import-jdk-stubs:

build-classes-javac:
    [mkdir] Created dir: /home/pavel/jdk7/langtools/build/gensrc
    [mkdir] Created dir: /home/pavel/jdk7/langtools/build/classes
 [pcompile] Generating 7 resource files to /home/pavel/jdk7/langtools/build/gensrc
     [copy] Copying 1 file to /home/pavel/jdk7/langtools/build/gensrc
 [pcompile] Generating 1 resource files to /home/pavel/jdk7/langtools/build/gensrc
    [javac] Compiling 297 source files to /home/pavel/jdk7/langtools/build/classes
     [copy] Copying 3 files to /home/pavel/jdk7/langtools/build/classes

-def-build-java-launcher:

build-javac:
    [mkdir] Created dir: /home/pavel/jdk7/langtools/dist/lib
      [jar] Building jar: /home/pavel/jdk7/langtools/dist/lib/javac.jar
    [mkdir] Created dir: /home/pavel/jdk7/langtools/dist/bin
     [copy] Copying 1 file to /home/pavel/jdk7/langtools/dist/bin
     [copy] Copying 1 file to /home/pavel/jdk7/langtools/dist/bin

build-classes-javadoc:
 [pcompile] Generating 3 resource files to /home/pavel/jdk7/langtools/build/gensrc
    [javac] Compiling 72 source files to /home/pavel/jdk7/langtools/build/classes

build-javadoc:
      [jar] Building jar: /home/pavel/jdk7/langtools/dist/lib/javadoc.jar
     [copy] Copying 1 file to /home/pavel/jdk7/langtools/dist/bin

build-classes-doclets:
 [pcompile] Generating 6 resource files to /home/pavel/jdk7/langtools/build/gensrc
    [javac] Compiling 132 source files to /home/pavel/jdk7/langtools/build/classes
     [copy] Copying 2 files to /home/pavel/jdk7/langtools/build/classes

build-doclets:
      [jar] Building jar: /home/pavel/jdk7/langtools/dist/lib/doclets.jar

build-classes-javah:
 [pcompile] Generating 3 resource files to /home/pavel/jdk7/langtools/build/gensrc
     [copy] Copying 1 file to /home/pavel/jdk7/langtools/build/gensrc
 [pcompile] Generating 1 resource files to /home/pavel/jdk7/langtools/build/gensrc
    [javac] Compiling 16 source files to /home/pavel/jdk7/langtools/build/classes

build-javah:
      [jar] Building jar: /home/pavel/jdk7/langtools/dist/lib/javah.jar
     [copy] Copying 1 file to /home/pavel/jdk7/langtools/dist/bin

build-classes-javap:
 [pcompile] Generating 1 resource files to /home/pavel/jdk7/langtools/build/gensrc
     [copy] Copying 1 file to /home/pavel/jdk7/langtools/build/gensrc
 [pcompile] Generating 1 resource files to /home/pavel/jdk7/langtools/build/gensrc
    [javac] Compiling 75 source files to /home/pavel/jdk7/langtools/build/classes
     [copy] Copying 1 file to /home/pavel/jdk7/langtools/build/classes

build-javap:
      [jar] Building jar: /home/pavel/jdk7/langtools/dist/lib/javap.jar
     [copy] Copying 1 file to /home/pavel/jdk7/langtools/dist/bin

build-classes-apt:
 [pcompile] Generating 3 resource files to /home/pavel/jdk7/langtools/build/gensrc
    [javac] Compiling 109 source files to /home/pavel/jdk7/langtools/build/classes
    [javac] Note: Some input files use or override a deprecated API.
    [javac] Note: Recompile with -Xlint:deprecation for details.
     [copy] Copying 1 file to /home/pavel/jdk7/langtools/build/classes

build-apt:
      [jar] Building jar: /home/pavel/jdk7/langtools/dist/lib/apt.jar
     [copy] Copying 1 file to /home/pavel/jdk7/langtools/dist/bin

build-all-tools:

BUILD SUCCESSFUL
Total time: 2 minutes 13 seconds

V adresáři langtools by se po překladu měly mj. objevit i nové podadresáře pojmenované dist/bin/ a dist/lib/. Nás bude zajímat především adresář dist/bin, v němž se nachází skripty pojmenované java, javac atd., které volají stejně pojmenované nástroje z JDK 7, ovšem s vhodně nastavenými cestami k javovským knihovnám, v nichž je projekt Lambda implementován. Demonstrační příklady uvedené v následujících kapitolách tedy budeme překládat skriptem langtools/dis­t/bin/javac a výsledný přeložený bajtkód se bude spouštět pomocí langtools/dis­t/bin/java!

4. Postupný vývoj syntaxe a sémantiky použité pro zápis anonymních funkcí

O možnostech přidání podpory pro anonymní funkce a uzávěry do programovacího jazyka Java se hovořilo již několik let, minimálně od doby, kdy začaly vznikat dynamicky typované programovací jazyky určené pro běh nad virtuálním strojem Javy (JVM). Jedná se především o jazyky Jython (1997), Groovy (2003), Scala (2004), JRuby (2006), JavaScript (Rhino) a v neposlední řadě taktéž o dialekt Lispu nazvaný Clojure (2007). Ovšem teprve v roce 2008 vzniklo několik návrhů syntaxe a sémantiky použité jak při definici anonymních funkcí, tak i při jejich volání. Tyto návrhy nejsou vzájemně kompatibilní – netýká se to pouze syntaxe (ta je v podstatě poměrně nedůležitá, i když viditelná ve zdrojových kódech), ale především sémantiky, tj. vlastností anonymních funkcí a způsobů jejich použití. Jednotlivé návrhy pro zavedení podpory anonymních funkcí do Javy lze získat v mnoha formách (blogy, prezentace, články). Oficiální podoba návrhů je dostupná na těchto odkazech:

  1. Concise Instance Creation Expressions: Closures without Complexity
    http://docs.go­ogle.com/Doc.as­px?id=k73_1ggr36h
  2. Closures for the Java Programming Language (v0.5)
    http://www.ja­vac.info/closu­res-v05.html
  3. First-class methods: Java-style closures
    https://docs.go­ogle.com/Doc?id=­ddhp95vd_6hg3qhc
  4. Project Lambda: Straw-Man Proposal
    http://cr.open­jdk.java.net/~mr/lam­bda/straw-man/

Další zajímavé dokumenty, které se týkají anonymních funkcí v Javě, lze najít zde:

  1. Better closures (for Java)
    http://blogs.sun­.com/jrose/en­try/better_clo­sures
  2. Lambdas in Java: An In-Depth Analysis
    http://www.in­foq.com/articles/lam­bdas-java-analysis

5. Návrh Marka Reinholda (Sun, Oracle) – slavný „straw-man proposal“

Velmi zajímavý byl návrh implementace anonymních funkcí pocházející od Marka Reinholda z firmy Sun Microsystems a později Oracle. Tento návrh je známý také pod jménem „straw-man proposal“ (Mark ho prý vytvořil poměrně rychle v letadle při cestě na konferenci). V této variantě implementace anonymních funkcí je možné vytvářet lambda výrazy v jediném řádku a následně (pokud je to samozřejmě zapotřebí) přiřadit lambda výraz do proměnné nebo atributu mající speciální typ. Každý lambda výraz měl v tomto návrhu následující podobu:

#(parametry) (tělo výrazu bez explicitního příkazu return)
#(parametry) {tělo výrazu s příkazem return}

Při pohledu na předchozí ukázku si můžeme povšimnout, že znak # nahrazuje symbol λ a parametry (v lambda kalkulu se nazývají vázané proměnné) jsou od těla výrazu odděleny pomocí kulatých závorek, které nahrazují symbol tečky, jež se používá v lambda kalkulu (v jiných programovacích jazycích a taktéž v návrhu popsaném v dalších kapitolách se můžeme setkat například s použitím symbolů ⇒ nebo ->). Lambda výraz bylo možné přiřadit do proměnné či atributu s typem, který se zapisoval následovně:

#typ_návratové_hodnoty(seznam typů parametrů lambda výrazu)

Pokud se anonymní funkce přiřazená do proměnné či atributu měla skutečně zavolat, musel se za její jméno a před skutečné parametry vložit znak tečky:

lambda_výraz.(skutečné parametry)

Ukažme si nyní několik příkladů:

Lambda výraz bez parametrů vracející konstantu:

#() (42)

Lambda výraz s jedním parametrem, pouze zapsaný a nikam nepřiřazený, nevyhodnocovaný:

#(int x) (x+1);

Lambda výraz s dvojicí parametrů:

#(int x, int y) (x*y);

Zachycení proměnné z obalujícího lexikálního rozsahu platnosti:

int y = 42;
inc = #(int x) (x+y);

Lambda výraz s tělem obsahujícím více příkazů a explicitní return:

#(int x, int y){ int z = expensiveComputation(x, y);
                 if (z < 0) return x;
                 if (z < 0) return y;
                 return 0; }

Přiřazení lambda výrazu do proměnné či atributu (povšimněte si typu této proměnné/atributu):

#int(int) inc = #(int x) (x+1);

Vyhodnocení lambda výrazu (s tečkou před závorkou s parametry!):

int y = inc.(42)

6. Problematické části návrhu Marka Reinholda aneb lze vůbec vhodně zařadit lambda výrazy do Javy?

V Markově návrhu si můžeme všimnout několika problematických částí vyplývajících především ze stávající syntaxe a sémantiky programovacího jazyka Java. Poměrně mnoho komentátorů tohoto návrhu si například přálo, aby se namísto znaku # použil znak $, nebo aby se přímo psalo nové klíčové slovo lambda. Přidání nového klíčového slova by však bylo problematické, protože by mohlo dojít ke komplikacím při použití aplikací, v jejichž zdrojových textech se tento identifikátor používá pro pojmenování proměnných, atributů, metod, rozhraní nebo tříd (snaha o minimalizaci zásahu do této části syntaxe jazyka je zřejmá už jen při pohledu na to, kolik různých významů nakonec získalo klíčové slovo final). Podobně problematická je však i změna významu znaku $, protože tento znak může být (i když se jedná o poměrně neznámou a málo používanou vlastnost) součástí identifikátorů. Následující příklad je zcela legálním a korektním Javovským programem:

class $
{
}

public class DolarTest
{
    private int $ = 42;
    private int $_$ = 6502;
    private $ $1 = new $();

    private int $$(int $)
    {
        return $<<1;
    }
}

Druhý problém nastal při návrhu způsobu volání lambda výrazu přiřazeného do proměnné. V tomto případě bylo nutné volání lambda výrazu odlišit od volání funkce a to z jednoduchého důvodu – programovací jazyk Java má totiž oddělené jmenné prostory pro atributy, proměnné a funkce, což například znamená, že je možné napsat a přeložit následující program:

public class NamespaceTest
{
    // jmenný prostor atributů
    public int foo = 42;

    // jmenný prostor funkcí je oddělený:
    // - nenastane kolize jmen
    public int foo()
    {
        return this.foo;
    }

    public static void main(String[] args)
    {
        NamespaceTest test = new NamespaceTest();
        System.out.println(test.foo);
        System.out.println(test.foo());
    }
}

Povšimněte si zejména toho, jakým způsobem překladač rozliší při volání metody System.out.prin­tln(), zda má vytisknout hodnotu atributu foo nebo návratovou hodnotu metody foo(). Při zavedení nového typu lambda výrazů přiřazených do atributů/proměn­ných, se však musí volání lambda výrazu odlišit od volání funkce:

public class Incrementer
{
    private static #int(int) inc = #(int x) (x+1);

    private static int inc(int x)
    {
        return x + 2;
    }

    public static void main(String[] args)
    {
        // vypise se 4 nebo 5?
        System.out.println(Incrementer.inc(3));
    }
}

Právě z tohoto důvodu Mark navrhl použití tečky před výrazem, aby se jeho identifikátor mohl odlišit od shodného identifikátoru funkce. S použitím tečky je vše zřejmé, i když na úkor „divné“ syntaxe:

public class Incrementer
{
    private static #int(int) inc = #(int x) (x+1);

    private static int inc(int x)
    {
        return x + 2;
    }

    public static void main(String[] args)
    {
        // vypise se 4 - vola se metoda inc()
        System.out.println(Incrementer.inc(3));
        // vypise se 5 - vyvola se lambda vyraz
        System.out.println(Incrementer.inc.(3));
    }
}

7. Současná podoba implementace anonymních funkcí

I přes některé přednosti návrhu Marka Reinholda byla nakonec pro projekt Lambda vybrána mnohem jednodušší syntaxe a sémantika zápisu anonymních funkcí. Tvůrci této varianty nechtěli, aby se přidání anonymních funkcí do Javy projevilo v nutnosti změn ve struktuře generovaného bajtkódu a taktéž nechtěli do jazyka přinést mnoho zcela nových konstrukcí (viz ukázky z předchozí kapitoly). Z tohoto důvodu jsou anonymní funkce v současné verzi projektu Lambda interně reprezentovány jako anonymní třídy s jedinou metodou. Anonymní třídy jsou samozřejmě součástí Javy již od verze 1.1, ovšem jejich použití v praxi je poměrně složité a především nepřehledné – pro vytvoření instance dané třídy se totiž používá následující nepříliš čitelná konstrukce:

new Comparator<String>() {
    // konkrétní implementace metody compare,
    // jejíž hlavička je umístěna v rozhraní
    // nebo abstraktní třídě Comparator
    public int compare(String str1, String str2) {
        return str1.length() - str2.length();
    }
}

Metoda v anonymní třídě má přístup k atributům své obalující třídy:

new MouseAdapter() {
    public void mouseClicked(MouseEvent e) {
        if (e.getButton() == MouseEvent.NOBUTTON)
        {
            textArea.setText("No button clicked...");
        } else if (e.getButton() == MouseEvent.BUTTON1) {
            textArea.setText("Button 1 clicked...");
        } else if (e.getButton() == MouseEvent.BUTTON2) {
            textArea.setText("Button 2 clicked...");
        } else if (e.getButton() == MouseEvent.BUTTON3) {
            textArea.setText("Button 3 clicked...");
        }
        textArea.append("Number of click: " + e.getClickCount());
        textArea.append("Click position (X, Y):  " + e.getX() + ", " + e.getY());
    }
}

Vývojáři projektu Lambda provedli analýzu zdrojových kódů jak standardních knihoven jazyka Java, tak i některých dalších větších projektů (například zdrojových kódů vývojového prostředí Netbeans) a zjistili, že velká část anonymních tříd obsahuje pouze jedinou implementovanou metodu – typickým příkladem je anonymní třída implementující rozhraní Runnable, Comparator (pokud se implementuje pouze metoda compare a ne equalTo, což není vždy nutné), většina listenerů při práci s grafickým uživatelským rozhraním (MouseListener), různé typy filtrů atd. Právě při tvorbě těchto typů anonymních tříd, které jsou kvůli existenci jediné implementované metody někdy nazývány Single Abstract Method neboli SAM, je možné použít programové konstrukce projektu Lambda.

8. SAM a jejich inicializace

Podívejme se nyní, jakým způsobem by se dala vytvořit jednoduchá anonymní funkce (lambda výraz), která vrací svůj parametr zvětšený o jedničku. Pro jednoduchost předpokládejme, že se jedná o celočíselný parametr typu int. V pojetí projektu Lambda je nejprve nutné vytvořit rozhraní nebo abstraktní třídu s jedinou libovolně pojmenovanou metodou mající parametr typu int a vracející hodnotu typu int:

interface SAM1
{
    int exec(int param);
}

popř. pokud čtenář preferuje anonymní třídy:

abstract class SAM2
{
    abstract int exec(int param);
}

Pro vytvoření lambda výrazu lze využít novou syntaxi:

SAM1 foo = #{ int x -> x + 1 };
SAM2 bar = #{ int x -> x * x };

A následně vytvořené lambda výrazy použít:

int x = foo.exec(10);
System.out.println(x);

System.out.println(bar2.exec(20));

Terminologie se nám ovšem poněkud komplikuje, protože například proměnná foo je anonymní funkce (lambda výraz), který má typ rozhraní SAM1 a je interně implementován jako anonymní třída :-)

Ukažme si ještě poněkud složitější příklad, kde je mj. ukázáno i použití rozhraní Runnable:

Cloud 24 - tip 1

// při práci s lambda výrazy prozatím nelze použít vnitřní abstraktní třídy
abstract class SAM3
{
    abstract int method(int param);
}

public class LambdaTest
{
    interface SAM
    {
        int method(int param);
    }

    interface SAM2
    {
        int method(int x, int y);
    }

    public static void main(String[] args)
    {
        SAM sam1 = #{ int x -> x + 1 };

        int x = sam1.method(10);
        System.out.println(x);
        System.out.println(sam1.method(42));

        SAM2 sam2 = #{ int x, int y -> x * y };
        System.out.println(sam2.method(42, 3));

        Runnable r = #{ System.out.println("Blah") };
        r.run();

        SAM3 sam3 = #{ int x -> x * x };
        System.out.println(sam3.method(42));
    }
}

Lambda výrazy samozřejmě mají přístup k atributům obalové třídy, což je, jak si řekneme příště, poměrně důležitá vlastnost:

public class LambdaTest2
{
    private int y = 0;

    interface SAM4
    {
        int exec(int param);
    }

    public void run()
    {
        SAM4 c = #{ int x -> x + y };
        System.out.println(c.exec(10));
        y = 10;
        System.out.println(c.exec(10));
    }

    public static void main(String[] args)
    {
        new LambdaTest2().run();
    }
}

Prozatím může zavedení nové syntaxe vypadat jako pouhá hračka pro milovníky funkcionálních jazyků, ovšem ve skutečnosti se jedná o velmi silnou a užitečnou jazykovou konstrukci, jejíž význam si ukážeme příště. Jedním z pěkných příkladů bude například implementace smyčky typu for-each, která se ve skutečnosti bude moci provádět paralelně – každá iterace smyčky může teoreticky proběhnout v samostatném vlákně na samostatném procesorovém jádru.

9. Odkazy na Internetu

  1. Java™ Platform, Standard Edition 7 Binary Snapshot Releases
    http://dlc.sun­.com.edgesuite­.net/jdk7/bina­ries/index.html
  2. Trying the prototype
    http://mail.o­penjdk.java.net/pi­permail/lambda-dev/2010-August/002179.html
  3. Better closures (for Java)
    http://blogs.sun­.com/jrose/en­try/better_clo­sures
  4. Lambdas in Java: An In-Depth Analysis
    http://www.in­foq.com/articles/lam­bdas-java-analysis
  5. Class ReflectiveOpe­rationExcepti­on
    http://downlo­ad.java.net/jdk7/doc­s/api/java/lan­g/ReflectiveO­perationExcep­tion.html
  6. Proposal: Indexing access syntax for Lists and Maps
    http://mail.o­penjdk.java.net/pi­permail/coin-dev/2009-March/001108.html
  7. Proposal: Elvis and Other Null-Safe Operators
    http://mail.o­penjdk.java.net/pi­permail/coin-dev/2009-March/000047.html
  8. Java 7 : Oracle pushes a first version of closures
    http://www.bap­tiste-wicht.com/2010/05­/oracle-pushes-a-first-version-of-closures/
  9. Groovy: An agile dynamic language for the Java Platform
    http://groovy­.codehaus.org/O­perators
  10. Better Strategies for Null Handling in Java
    http://www.sli­deshare.net/Step­han.Schmidt/bet­ter-strategies-for-null-handling-in-java
  11. Control Flow in the Java Virtual Machine
    http://www.ar­tima.com/under­thehood/flowP­.html
  12. Java Virtual Machine
    http://en.wiki­pedia.org/wiki/Ja­va_virtual_machi­ne
  13. ==, .equals(), compareTo(), and compare()
    http://leepoin­t.net/notes-java/data/expres­sions/22compa­reobjects.html
  14. New JDK7 features
    http://openjdk­.java.net/pro­jects/jdk7/fe­atures/
  15. Project Coin: Bringing it to a Close(able)
    http://blogs.sun­.com/darcy/en­try/project_co­in_bring_close
  16. ClosableFinder source code
    http://blogs.sun­.com/darcy/re­source/Projec­tCoin/Closeable­Finder.java
  17. Joe Darcy blog about JDK
    http://blogs.sun­.com/darcy
  18. Java 7 – more dynamics
    http://www.bap­tiste-wicht.com/2010/04­/java-7-more-dynamics/
  19. ArrayList (JDK 1.4)
    http://downlo­ad.oracle.com/ja­vase/1.4.2/doc­s/api/java/util/A­rrayList.html

Byl pro vás článek přínosný?

Autor článku

Vystudoval VUT FIT a v současné době pracuje na projektech vytvářených v jazycích Python a Go.