Hlavní navigace

Novinky v JDK 7 (5) – projekt Lambda a anonymní funkce v příkladech

9. 12. 2010
Doba čtení: 19 minut

Sdílet

V páté části seriálu o nových vlastnostech programovacího jazyka Java i jeho virtuálního stroje budeme pokračovat v popisu použití anonymních funkcí, které jsou implementovány v rámci projektu Lambda. Mezi typické možnosti využití anonymních funkcí patří implementace komparátorů, filtrů a podobně.

Obsah

1. Projekt Lambda a anonymní funkce v příkladech

2. Rychlé vytvoření a spuštění vlákna

3. Obejití nutnosti reagovat na kontrolované výjimky

4. Implementace komparátorů kompatibilních s JDK 1.2 až JDK 1.4

5. Implementace komparátorů využívající nových možností JDK 5 a JDK 6

6. Využití anonymních funkcí pro implementaci komparátorů

7. Rozdíl v bajtkódu generovaného při překladu prvního a druhého demonstračního příkladu

8. Anonymní funkce v roli filtrů

9. Odkazy na Internetu

1. Projekt Lambda a anonymní funkce v příkladech

předchozí části seriálu o novinkách připravovaných pro JDK 7 i o vlastnos­tech, které nakonec nebyly do této verze JDK zahrnuty, jsme si řekli základní informace o projektu Lambda, jehož cílem je zavedení podpory anonymních funkcí (či metod) do Javy. Poslední podoba syntaxe a sémantiky anonymních funkcí, která je v projektu Lambda implementována, je založena na anonymních třídách (které lze používat již od dob JDK 1.1) s jedinou implementovanou metodou. Třídy s těmito vlastnostmi se nazývají Single Abstract Method neboli zkráceně SAM. SAM se při programování v Javě používají poměrně často, ať již při implementaci komparátorů (anonymních tříd, jejichž jediná metoda je zavolána při řazení prvků v poli či kolekci), různých filtrů, kódu reagujícího na událost, která vznikne jako reakce na operace prováděné uživatelem v grafickém uživatelském rozhraní atd.

Následuje typický příklad použití SAM v takzvaném event handleru, který dokáže zachytit pozici kurzoru myši ve chvíli, kdy uživatel stiskne některé její tlačítko, a následně ji uložit do finálních atributů objektu, v němž je instance třídy MouseAdapter použita:

p.addMouseListener(new MouseAdapter()
{
    public void mouseClicked(MouseEvent e)
    {
        x = e.getX();
        y = e.getY();
    }
});

V rámci projektu Lambda byla pro vytváření SAM zavedena poněkud jednodušší (a především kratší) syntaxe, která spočívá v tom, že se mezi dvojici znaků #{ a } zapíšou jak parametry implementované metody (samozřejmě včetně jejich typů), tak i vlastní tělo metody. Pokud lze celou metodu zapsat pouze jediným příkazem typu return výraz, je možné celý zápis ještě více zkrátit a namísto těla SAM zapsat pouze výraz bez příkazu return. Každá takto vytvořená SAM musí implementovat buď nějaké rozhraní s jednou předepsanou metodou, nebo abstraktní třídu, taktéž s jednou metodou. Následující příklad slouží pro připomenutí syntaxe zápisu SAM:

public class Lambda1
{
    interface SAM
    {
        int method(int param);
    }
 
    interface SAM2
    {
        int method(int x, int y);
    }
 
    public static void main(String[] args)
    {
        // namísto těla funkce se použije pouze výraz
        SAM sam1a = #{ int x -> x + 1 };
 
        // zde je použito tělo funkce s jediným příkazem return
        SAM sam1b = #{ int x -> return x + 1 };
 
        int x = sam1.method(10);
        System.out.println(x);
        System.out.println(sam1a.method(42));
        System.out.println(sam1b.method(42));
 
        // dvojice parametrů, namísto těla funkce se použije pouze výraz
        SAM2 sam2a = #{ int x, int y  -> x * y };
 
        // zde je použito tělo funkce s jediným příkazem return
        SAM2 sam2b = #{ int x, int y  -> return x * y };
 
        System.out.println(sam2a.method(42, 3));
        System.out.println(sam2b.method(42, 3));
    }
}

2. Rychlé vytvoření a spuštění vlákna

Novou syntaxi pro zjednodušenou tvorbu anonymních tříd s jedinou metodou (SAM) lze využít mnoha různými způsoby. V některých aplikacích – a nemusí se jednat pouze o implementaci serveru, ale například i o časově náročný a přitom dobře paralelizovatelný výpočet – se například často vytváří nová vlákna, která však mají provést pouze několik příkazů. V tomto případě je možné použít například způsob zápisu ukázaný v následujícím demonstračním příkladu. V tomto příkladu se nejdříve s využitím syntaxe projektu Lambda vytvoří anonymní třídy implementující rozhraní Runnable. Jedná se tedy o objekty mající pouze jedinou metodu run, která je následně pomocí příkazu new Thread(runnable)­.start() spuštěna v novém vlákně. Pokud by se z těla vlákna mělo přistupovat ke sdíleným objektům nebo jejich atributům, je samozřejmě možné – a většinou i nutné – použít všechny možnosti synchronizace, které Java programátorům nabízí, popř. lze v některých případech využít modifikátor volatile:

public class ThreadTest1
{
    public static void main(String[] args)
    {
        // Vytvoření čtyř objektů implementujících rozhraní Runnable.
        // To znamená, že každý vytvořený objekt obsahuje pouze jedinou metodu run().
        Runnable r1 = #{ Thread.sleep(3000); System.out.println("Prvni vlakno"); };
        Runnable r2 = #{ Thread.sleep(2000); System.out.println("Druhe vlakno"); };
        Runnable r3 = #{ Thread.sleep(1000); System.out.println("Treti vlakno"); };
        Runnable r4 = #{ Thread.sleep(500);  System.out.println("Ctvrte vlakno"); };
 
        System.out.println("-----------");
        new Thread(r1).start();
        System.out.println("-----------");
        new Thread(r2).start();
        System.out.println("-----------");
        new Thread(r3).start();
        System.out.println("-----------");
        new Thread(r4).start();
        System.out.println("-----------");
    }
}

Tento program po svém spuštění nejdříve čtyřikrát vypíše řadu pomlček, protože tento kód běží v hlavním vlákně bez pozastavení. Posléze se postupně (s časovým zpožděním) vypíšou řetězce „Ctvrte vlakno“, „Treti vlakno“ atd. Pořadí výpisu těchto řetězců sice není zaručeno, ovšem s velkou pravděpodobností se nejdříve vypíše text ze čtvrtého vlákna, posléze ze třetího, druhého a nakonec z vlákna prvního:

tisnik@bender:~/java/jdk7/langtools/dist/bin$ ./java ThreadTest1
-----------
-----------
-----------
-----------
-----------
Ctvrte vlakno
Treti vlakno
Druhe vlakno
Prvni vlakno

3. Obejití nutnosti reagovat na kontrolované výjimky

Ve zdrojovém kódu demonstračního příkladu uvedeného v předchozí kapitole si povšimněte ještě jedné změny oproti situaci, kdy by se SAM vytvářela takovým způsobem, jak bylo v JDK 6 i v předchozích verzích JDK zvykem, například takto new Runnable() {public void run() {…}};. V tomto případě by bylo nutné volání Thread.sleep() umístit do bloku try-catch, protože při čekání vlákna na uplynutí zadaného časového intervalu může být signalizováno z vnějšku (například zavoláním Thread.interrup­t()), že má být vlákno ukončeno, což ve vlákně vede ke vzniku výjimky typu InterruptedEx­ception.

Pokud se však použije způsob vytvoření SAM podporovaný projektem Lambda, není striktně vyžadováno tuto výjimku zachycovat, i když to je samozřejmě možné a v mnoha případech i nutné pro korektní práci programu! Je tomu tak z toho důvodu, že autoři projektu Lambda odstranili povinnost zpracovávat kontrolované výjimky (checked exceptions), tj. výjimky, jejichž zpracování je hlídáno překladačem již v době překladu zdrojových textů do bajtkódu. Existence kontrolovaných výjimek sice bezesporu vede k tvorbě robustnějšího kódu, ovšem na druhou stranu existují situace, kdy je zpracování některých typů výjimek ryze formální záležitostí:

public class Threads
{
    public static void main(String[] args)
    {
        Runnable r1 = new Runnable() {
            public void run() {
                try {
                    Thread.sleep(3000);
                }
                catch (InterruptedException e) {}
                System.out.println("Prvni vlakno");
            }
        };
        Runnable r2 = new Runnable() {
            public void run() {
                try {
                    Thread.sleep(2000);
                }
                catch (InterruptedException e) {}
                System.out.println("Druhe vlakno");
            }
        };
        Runnable r3 = new Runnable() {
            public void run() {
                try {
                    Thread.sleep(1000);
                }
                catch (InterruptedException e) {}
                System.out.println("Treti vlakno");
            }
        };
        Runnable r4 = new Runnable() {
            public void run() {
                try {
                    Thread.sleep(500);
                }
                catch (InterruptedException e) {}
                System.out.println("Ctvrte vlakno");
            }
        };
 
        new Thread(r1).start();
        System.out.println("-----------");
        new Thread(r2).start();
        System.out.println("-----------");
        new Thread(r3).start();
        System.out.println("-----------");
        new Thread(r4).start();
        System.out.println("-----------");
    }
}

Poznámka: vyvolání výjimky typu InterruptedEx­ception současně vede k vymazání příznaku, že vlákno bylo přerušeno. To znamená, že pokud se například nějaký výpočet provádí ve smyčce s testem na ukončení vlákna z vnějšku, je vhodné na výjimku typu InterruptedEx­ception korektně reagovat, například nastavením lokální pravdivostní proměnné, která je použita k rozhodnutí, zda a kdy má být vlákno ukončeno. Popř. lze přímo v obsluze výjimky zavolat příkaz Thread.curren­tThread().inte­rrupt();, aby se obnovil příznak nesoucí stav přerušení vlákna.

4. Implementace komparátorů kompatibilní s JDK 1.2 až JDK 1.4

Druhou oblastí, v níž se můžeme setkat s použitím SAM, je vytváření vlastních komparátorů, které mohou být použity například při řazení prvků uložených v polích nebo v kolekcích (typicky v seznamech). Nejprve si ukážeme, jak se komparátory vytvářely v JDK 1.2, kde bylo rozhraní – pojmenované poněkud nešťastně Comparator – poprvé zařazeno do veřejného API. Dále uvedený způsob byl používán až do JDK 1.4. Nejprve si vytvoříme pomocnou třídu s informacemi o zaměstnancích. Jedná se o typickou třídu používanou v demonstračních příkladech (ovšem na rozdíl od mnoha jiných implementací jsem zvolil třídu s finálními atributy), jejíž instance budeme ukládat do seznamu a posléze třídit podle různých kritérií:

class Employee
{
    private final String name;
    private final int age;
    private final int salary;
 
    public Employee(String name, int age, int salary)
    {
        this.name = name;
        this.age = age;
        this.salary = salary;
    }
 
    public String getName()
    {
        return this.name;
    }
 
    public int getAge()
    {
        return this.age;
    }
 
    public int getSalary()
    {
        return this.salary;
    }
 
    @Override
    public String toString()
    {
        return String.format("%10s(%2d) ... $%d", this.getName(), this.getAge(), this.getSalary());
    }
}

Každý komparátor je v JDK 1.2 až JDK 1.4 představován třídou implementující rozhraní Comparator. Tato třída musí obsahovat metodu int compare(Object, Object), která vrací záporné číslo v případě, že je první objekt „menší než“ objekt druhý, hodnotu nula, pokud jsou objekty „stejné“ a kladné číslo, pokud je první objekt „větší než“ objekt druhý. Význam slov „menší než“, „stejný“ a „větší než“ samozřejmě záleží na tom, jakým způsobem vývojář metodu compare nadefinuje (může například porovnávat dvě komplexní čísla podle jejich absolutní hodnoty, vektory podle vypočtené délky, rodná čísla pouze v závislosti na prvních šesti cifrách, tj. ignorováním čtyř posledních „rozlišovacích“ cifer atd.):

class EmployeeComparatorByName implements Comparator
{
    public final int compare(Object o1, Object o2)
    {
        String name1 = ((Employee)o1).getName();
        String name2 = ((Employee)o2).getName();
        return name1.compareTo(name2);
        // popr. napriklad porovnani podle DELKY jmena
        // return name1.length() - name2.length();
    }
}
 
class EmployeeComparatorByAge implements Comparator
{
    public final int compare(Object o1, Object o2)
    {
        int age1 = ((Employee)o1).getAge();
        int age2 = ((Employee)o2).getAge();
        return age1 - age2;
    }
}
 
class EmployeeComparatorBySalary implements Comparator
{
    public final int compare(Object o1, Object o2)
    {
        int salary1 = ((Employee)o1).getSalary();
        int salary2 = ((Employee)o2).getSalary();
        return salary1 - salary2;
    }
}

Poznámka: pokud by měl být komparátor použit i v jiném případě, než pouze při volání metody Collections.sor­t(List list, Comparator c) popř. Arrays.sor­t(Object[] a, Comparator c), je žádoucí přetížit i metodu equals(), a to takovým způsobem, aby tato metoda vracela hodnotu true v případě, že metoda compareTo() vrací nulu.

Výše deklarované komparátory si můžeme snadno vyzkoušet:

public class SortTest1
{
 
    private void printEmployees(List<Employee> employees)
    {
        for (Employee employee : employees)
        {
            System.out.println(employee.toString());
        }
    }
 
    public void run()
    {
        List<Employee> employees = new ArrayList<Employee>();
        employees.add(new Employee("CEO",       50, 50000));
        employees.add(new Employee("Porter",    16, 10000));
        employees.add(new Employee("Tester",    35, 30000));
        employees.add(new Employee("Developer", 30, 40000));
 
        System.out.println("Original order:");
        printEmployees(employees);
 
        Collections.sort(employees, new EmployeeComparatorByName());
        System.out.println("\nSorted by name:");
        printEmployees(employees);
 
        Collections.sort(employees, new EmployeeComparatorByAge());
        System.out.println("\nSorted by age:");
        printEmployees(employees);
 
        Collections.sort(employees, new EmployeeComparatorBySalary());
        System.out.println("\nSorted by salary:");
        printEmployees(employees);
    }
 
    public static void main(String[] args)
    {
        new SortTest1().run();
    }
}

S následujícím výsledkem:

~/$ java SortTest1
Original order:
       CEO(50) ... $50000
    Porter(16) ... $10000
    Tester(35) ... $30000
 Developer(30) ... $40000
 
Sorted by name:
       CEO(50) ... $50000
 Developer(30) ... $40000
    Porter(16) ... $10000
    Tester(35) ... $30000
 
Sorted by age:
    Porter(16) ... $10000
 Developer(30) ... $40000
    Tester(35) ... $30000
       CEO(50) ... $50000
 
Sorted by salary:
    Porter(16) ... $10000
    Tester(35) ... $30000
 Developer(30) ... $40000
       CEO(50) ... $50000

5. Implementace komparátorů využívající nových možností JDK 5 a JDK 6

JDK 5 a JDK 6 má vývojář implementující komparátory poněkud snadnější situaci. Může totiž použít generiky a vytvořit komparátor specializovaný pouze na porovnání dvou objektů určitého typu nebo samozřejmě potomků dané třídy. Toto řešení má jednu podstatnou výhodu v tom, že není nutné provádět explicitní kontrolu typů (tu jsme stejně v předchozím příkladu neprováděli), navíc se v metodě compare() nemusí provádět přetypování z obecného objektu (Object) na konkrétní typ objektu:

class EmployeeComparatorByName implements Comparator<Employee>
{
    public final int compare(Employee o1, Employee o2)
    {
        String name1 = o1.getName();
        String name2 = o2.getName();
        return name1.compareTo(name2);
    }
}
 
class EmployeeComparatorByAge implements Comparator<Employee>
{
    public final int compare(Employee o1, Employee o2)
    {
        int age1 = o1.getAge();
        int age2 = o2.getAge();
        return age1 - age2;
    }
}
 
class EmployeeComparatorBySalary implements Comparator<Employee>
{
    public final int compare(Employee o1, Employee o2)
    {
        return o1.getSalary() - o2.getSalary();
    }
}
 
public class SortTest2
{
 
    private void printEmployees(List<Employee> employees)
    {
        for (Employee employee : employees)
        {
            System.out.println(employee.toString());
        }
    }
 
    public void run()
    {
        List<Employee> employees = new ArrayList<Employee>();
        employees.add(new Employee("CEO",       50, 50000));
        employees.add(new Employee("Porter",    16, 10000));
        employees.add(new Employee("Tester",    35, 30000));
        employees.add(new Employee("Developer", 30, 40000));
 
        System.out.println("Original order:");
        printEmployees(employees);
 
        Collections.sort(employees, new EmployeeComparatorByName());
        System.out.println("\nSorted by name:");
        printEmployees(employees);
 
        Collections.sort(employees, new EmployeeComparatorByAge());
        System.out.println("\nSorted by age:");
        printEmployees(employees);
 
        Collections.sort(employees, new EmployeeComparatorBySalary());
        System.out.println("\nSorted by salary:");
        printEmployees(employees);
    }
 
    public static void main(String[] args)
    {
        new SortTest2().run();
    }
}

Je ovšem zapotřebí si dát pozor na to, že překladač nekontroluje, zda se metodě Collections.sor­t(List list, Comparator c) skutečně předává seznam správného typu. Pokud tomu tak není, vyvolá se až za běhu programu výjimka typu ClassCastExcep­tion. Po mírné úpravě předchozího programu se sice zobrazí mnoho varování (volba -Xlint), ovšem skutečná chyba nastane až při běhu aplikace. Změna nastala pouze v metodě run(), takže zde bude uveden pouze výpis této metody:

    public void run()
    {
        // netypovany seznam!!!
        List employees = new ArrayList();
        employees.add(new Employee("CEO",       50, 50000));
        employees.add(new Employee("Porter",    16, 10000));
        employees.add(new Employee("Tester",    35, 30000));
        employees.add(new Employee("Developer", 30, 40000));
 
        // asi nastanou problemy :-)
        employees.add("toto neni dobry napad!");
 
        System.out.println("Original order:");
        printEmployees(employees);
 
        Collections.sort(employees, new EmployeeComparatorByName());
        System.out.println("\nSorted by name:");
        printEmployees(employees);
 
        Collections.sort(employees, new EmployeeComparatorByAge());
        System.out.println("\nSorted by age:");
        printEmployees(employees);
 
        Collections.sort(employees, new EmployeeComparatorBySalary());
        System.out.println("\nSorted by salary:");
        printEmployees(employees);
    }

Výsledek běhu aplikace:

Original order:
       CEO(50) ... $50000
    Porter(16) ... $10000
    Tester(35) ... $30000
 Developer(30) ... $40000
Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to Employee
    at SortTest2.printEmployees(SortTest2.java:71)
    at SortTest2.run(SortTest2.java:89)
    at SortTest2.main(SortTest2.java:106)

6. Využití anonymních funkcí pro implementaci komparátorů

Konečně se dostáváme k tomu, jak je možné komparátory vytvořit pomocí anonymních funkcí. První úprava výše uvedeného demonstračního příkladu spočívá v tom, že se namísto nových tříd EmployeeCompa­ratorByName, EmployeeCompa­ratorByAge a EmployeeCompa­ratorBySalary vytvoří v metodě run() lokální proměnné typu anonymní funkce, což je typ totožný s Comparator<Em­ployee>. Základní princip odvození typů byl vysvětlen v předchozí části tohoto seriálu. Zdrojový kód programu je v tomto případě stručnější (a podle skromného názoru autora tohoto článku i čitelnější, což je ovšem otázka zvyku):

    public void run()
    {
        Comparator<Employee> ageComparator = #{ Employee o1, Employee o2 ->
            return o1.getAge() - o2.getAge();
        };
 
        Comparator<Employee> nameComparator = #{ Employee o1, Employee o2 ->
            String name1 = o1.getName();
            String name2 = o2.getName();
            return name1.compareTo(name2);
        };
 
        Comparator<Employee> salaryComparator = #{ Employee o1, Employee o2 ->
            return o1.getSalary() - o2.getSalary();
        };
 
        List<Employee> employees = new ArrayList<Employee>();
        employees.add(new Employee("CEO",       50, 50000));
        employees.add(new Employee("Porter",    16, 10000));
        employees.add(new Employee("Tester",    35, 30000));
        employees.add(new Employee("Developer", 30, 40000));
 
        System.out.println("Original order:");
        printEmployees(employees);
 
        Collections.sort(employees, nameComparator);
        System.out.println("\nSorted by name:");
        printEmployees(employees);
 
        Collections.sort(employees, ageComparator);
        System.out.println("\nSorted by age:");
        printEmployees(employees);
 
        Collections.sort(employees, salaryComparator);
        System.out.println("\nSorted by salary:");
        printEmployees(employees);
    }

Ve skutečnosti však není nutné anonymní funkce ukládat do proměnných, ale lze je přímo při vytváření předat metodě Collections.sor­t(). Na tomto místě si prosím povšimněte, že se v následujícím programu nikde nepíše, jakého typu je #{ Employee o1, Employee o2 .....}. Správný typ si odvodí samotný překladač!

    public void run()
    {
        List<Employee> employees = new ArrayList<Employee>();
        employees.add(new Employee("CEO",       50, 50000));
        employees.add(new Employee("Porter",    16, 10000));
        employees.add(new Employee("Tester",    35, 30000));
        employees.add(new Employee("Developer", 30, 40000));
 
        System.out.println("Original order:");
        printEmployees(employees);

        Collections.sort(employees, #{ Employee o1, Employee o2 ->
            String name1 = o1.getName();
            String name2 = o2.getName();
            return name1.compareTo(name2);
        });
 
        System.out.println("\nSorted by name:");
        printEmployees(employees);
 
        Collections.sort(employees, #{ Employee o1, Employee o2 ->
            return o1.getAge() - o2.getAge();
        });
 
        System.out.println("\nSorted by age:");
        printEmployees(employees);
 
        Collections.sort(employees, #{ Employee o1, Employee o2 ->
            return o1.getSalary() - o2.getSalary();
        });
 
        System.out.println("\nSorted by salary:");
        printEmployees(employees);
    }
}

Již v úvodních kapitolách jsme si řekli, že pokud anonymní funkce obsahuje ve svém těle pouze příkaz return, lze tělo nahradit pouze jediným výrazem a zápis tak ještě více zjednodušit:

    public void run()
    {
        List<Employee> employees = new ArrayList<Employee>();
        employees.add(new Employee("CEO",       50, 50000));
        employees.add(new Employee("Porter",    16, 10000));
        employees.add(new Employee("Tester",    35, 30000));
        employees.add(new Employee("Developer", 30, 40000));
 
        System.out.println("Original order:");
        printEmployees(employees);
 
        Collections.sort(employees, #{ Employee o1, Employee o2 -> o1.getName().compareTo(o2.getName()) });
 
        System.out.println("\nSorted by name:");
        printEmployees(employees);
 
        Collections.sort(employees, #{ Employee o1, Employee o2 -> o1.getAge() - o2.getAge() });
 
        System.out.println("\nSorted by age:");
        printEmployees(employees);
 
        Collections.sort(employees, #{ Employee o1, Employee o2 -> o1.getSalary() - o2.getSalary() });
 
        System.out.println("\nSorted by salary:");
        printEmployees(employees);
    }

7. Rozdíl v bajtkódu generovaném při překladu prvního a druhého demonstračního příkladu

Podívejme se nyní pro zajímavost na to, jak jsou jednotlivé implementace komparátorů přeloženy do bajtkódu. Pro jednoduchost si porovnáme pouze implementaci komparátoru pro porovnání věku zaměstnanců. Bajtkód první implementace kompatibilní s JDK 1.2 až JDK 1.4, je přímočarý (poznámky jsou samozřejmě doplněny ručně a pokud hledáte význam prvního parametru metody, postačuje si vzpomenout na „skryté“ this, neboli na referenci na instanci třídy, pro níž se metoda volá):

Compiled from "SortTest1.java"
class EmployeeComparatorByAge extends java.lang.Object implements java.util.Comparator{
EmployeeComparatorByAge();
  Code:
   0:   aload_0
   1:   invokespecial   #1; //Method java/lang/Object."<init>":()V
   4:   return
 
public final int compare(java.lang.Object, java.lang.Object);
  Code:
   // *** druhy parametr metody - o1 ***
   0:   aload_1
 
   // *** kontrola, zda lze provest pretypovani ***
   1:   checkcast       #2; //class Employee
   4:   invokevirtual   #3; //Method Employee.getAge:()I
   7:   istore_3
 
   // *** treti parametr metody - o2 ***
   8:   aload_2
 
   // *** kontrola, zda lze provest pretypovani ***
   9:   checkcast       #2; //class Employee
   12:  invokevirtual   #3; //Method Employee.getAge:()I
   15:  istore  4
 
   // *** provedeni vlastniho vypoctu ***
   17:  iload_3
   18:  iload   4
   20:  isub
   21:  ireturn
}

Bajtkód „typovaného“ komparátoru popsaného v páté kapitole je již poněkud složitější. Můžeme zde vidět, že se kromě explicitně zapsané metody compare() s oběma parametry typu Employee automaticky vytvořila další (nefinální) metoda compare(), která má naopak oba parametry typu Object. V této druhé metodě jsou použity instrukce checkcast pro kontrolu typů obou parametrů. Tímto způsobem bylo možné vygenerovat bajtkód kompatibilní se staršími JRE:

CS24_early

Compiled from "SortTest2.java"
class EmployeeComparatorByAge extends java.lang.Object implements java.util.Comparator{
EmployeeComparatorByAge();
  Code:
   0:   aload_0
   1:   invokespecial   #1; //Method java/lang/Object."<init>":()V
   4:   return
 
public final int compare(Employee, Employee);
  Code:
   // *** druhy parametr metody - o1 ***
   0:   aload_1
 
   // *** volani metody bez kontroly pretypovani ***
   1:   invokevirtual   #2; //Method Employee.getAge:()I
   4:   istore_3
 
   // *** treti parametr metody - o2 ***
   5:   aload_2
 
   // *** volani metody bez kontroly pretypovani ***
   6:   invokevirtual   #2; //Method Employee.getAge:()I
   9:   istore  4
 
   // *** provedeni vlastniho vypoctu ***
   11:  iload_3
   12:  iload   4
   14:  isub
   15:  ireturn
 
public int compare(java.lang.Object, java.lang.Object);
  Code:
   0:   aload_0
   1:   aload_1
   2:   checkcast       #3; //class Employee
   5:   aload_2
   6:   checkcast       #3; //class Employee
   9:   invokevirtual   #4; //Method compare:(LEmployee;LEmployee;)I
   12:  ireturn
}

V případě, že je namísto nové třídy EmployeeCompa­ratorByAge ve zdrojovém kódu uvedena pouze anonymní funkce (viz předchozí kapitolu), je struktura bajtkódu poněkud odlišná. První rozdíl spočívá v tom, že se namísto bajtkódu uloženého v souboru EmployeeCompa­ratorByAge.class vytvoří soubor SortTest3$1.clas­s, tj. první anonymní třída deklarovaná v rámci třídy SortTest3. Druhý rozdíl spočívá v tom, že instance této třídy obsahuje referenci na instanci třídy SortTest3 uloženou v atributu this$0, jenž je automaticky naplňován v konstruktoru. Pravděpodobně jste již uhodli, k čemu tato reference slouží, ostatně jeden příklad použití můžete najít v první kapitole. Dále se již vygenerovaný bajtkód odlišuje od svých předchůdců pouze v tom, že nejsou použity lokální proměnné, ale výpočet se provádí přímo s hodnotami uloženými na lokálním zásobníku (což je korektní, protože se v anonymní funkci žádné proměnné skutečně nepoužily):

Compiled from "SortTest3.java"
class SortTest3$1 extends java.lang.Object implements java.util.Comparator {
  final SortTest3 this$0;
 
  SortTest3$1(SortTest3);
    Code:
       0: aload_0
       1: aload_1
       2: putfield      #1                  // Field this$0:LSortTest3;
       5: aload_0
       6: invokespecial #2                  // Method java/lang/Object."":()V
       9: return
 
  public int compare(Employee, Employee);
    Code:
   // *** druhy parametr metody - o1 ***
       0: aload_1
 
   // *** volani metody bez kontroly pretypovani ***
       1: invokevirtual #3                  // Method Employee.getAge:()I
 
   // *** treti parametr metody - o2 ***
       4: aload_2
 
   // *** volani metody bez kontroly pretypovani ***
       5: invokevirtual #3                  // Method Employee.getAge:()I
 
   // *** provedeni vlastniho vypoctu ***
       8: isub
       9: ireturn
 
  public int compare(java.lang.Object, java.lang.Object);
    Code:
       0: aload_0
       1: aload_1
       2: checkcast     #4                  // class Employee
       5: aload_2
       6: checkcast     #4                  // class Employee
       9: invokevirtual #5                  // Method compare:(LEmployee;LEmployee;)I
      12: ireturn
}

8. Anonymní funkce v roli filtrů

Další způsob použití anonymních funkcí může být v případě implementace různých filtrů. V následujícím kódu, který si podrobně popíšeme až příště, je ukázáno, jakým způsobem lze vytvořit filtr použitý při výpisu pouze těch zaměstnanců, jejichž atributy odpovídají zadané podmínce (věk, výše platu, pozice jména v abecedě). Povšimněte si, že filtru (resp. anonymní funkci) lze předat parametr či parametry a že filtry je možné v případě potřeby psát dosti obecně, například lze namísto Object param použít proměnný počet parametrů (varrags) atd.:

interface Filter
{
    public boolean filter(Employee e, Object param);
}

public class FilterTest4
{

    private void printEmployees(List<Employee> employees, Filter filter, Object param)
    {
        for (Employee employee : employees)
        {
            if (filter.filter(employee, param))
            {
                System.out.println(employee.toString());
            }
        }
    }

    public void run()
    {
        List<Employee> employees = new ArrayList<Employee>();
        employees.add(new Employee("CEO",       50, 50000));
        employees.add(new Employee("Porter",    16, 10000));
        employees.add(new Employee("Tester",    35, 30000));
        employees.add(new Employee("Developer", 30, 40000));

        System.out.println("\nOlder employees:");
        printEmployees(employees, #{ Employee o, Object age -> o.getAge() > (Integer)age}, 30);

        System.out.println("\nYounger employees:");
        printEmployees(employees, #{ Employee o, Object age -> o.getAge() < (Integer)age}, 30);

        System.out.println("\nRich employees:");
        printEmployees(employees, #{ Employee o, Object salary -> o.getSalary() > (Integer)salary}, 35000);

        System.out.println("\nAt the beginning of alphabet:");
        printEmployees(employees, #{ Employee o, Object chr -> o.getName().charAt(0) < (Character)chr}, 'E');
    }

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

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.