Hlavní navigace

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

Pavel Tišnovský 9. 12. 2010

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:

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
Našli jste v článku chybu?

9. 12. 2010 15:39

Pokud se pouze pretizi equals(), tak to pracovat bude, protoze se pri pristupu k prvkum nejdrive kontroluje hashCode(), ktery musi byt shodny soucasne s equals(). I kdyz se samozrejme jedna o krehky kod, ktery zavisi na tom, ze hashCode() pro kazda dve sestimistna cisla je rozdilny - coz je, ale pro jine JDK to nemusi platit ;-)

9. 12. 2010 14:34

Je to tak, v realne aplikaci by se urcite musely dat kontroly v tomto pripade do konstruktoru. Pravdepodobne nejenom pro zaporna cisla, ale i pro nejaky vhodny rozsah veku a platu.

Ale je urcite dobre, ze jste na to upozornil, pokud kontroly pro hodnoty atributu chybi, je zadelano na problem.

Podnikatel.cz: Přehledná titulka, průvodci, responzivita

Přehledná titulka, průvodci, responzivita

Root.cz: 250 Mbit/s po telefonní lince, když máte štěstí

250 Mbit/s po telefonní lince, když máte štěstí

Lupa.cz: Seznam mění vedení. Pavel Zima v čele končí

Seznam mění vedení. Pavel Zima v čele končí

Vitalia.cz: Tesco: Chudá rodina si koupí levné polské kuře

Tesco: Chudá rodina si koupí levné polské kuře

Podnikatel.cz: Víme první výsledky doby odezvy #EET

Víme první výsledky doby odezvy #EET

Vitalia.cz: Mondelez stahuje rizikovou čokoládu Milka

Mondelez stahuje rizikovou čokoládu Milka

Měšec.cz: Jak vymáhat výživné zadarmo?

Jak vymáhat výživné zadarmo?

Měšec.cz: Zdravotní a sociální pojištění 2017: Připlatíte

Zdravotní a sociální pojištění 2017: Připlatíte

Vitalia.cz: Dáte si jahody s plísní?

Dáte si jahody s plísní?

DigiZone.cz: Sony KD-55XD8005 s Android 6.0

Sony KD-55XD8005 s Android 6.0

120na80.cz: Rakovina oka. Jak ji poznáte?

Rakovina oka. Jak ji poznáte?

Root.cz: Certifikáty zadarmo jsou horší než za peníze?

Certifikáty zadarmo jsou horší než za peníze?

Vitalia.cz: Láska na vozíku: Přitažliví jsme pro tzv. pečovatelky

Láska na vozíku: Přitažliví jsme pro tzv. pečovatelky

Lupa.cz: Insolvenční řízení kvůli cookies? Vítejte v ČR

Insolvenční řízení kvůli cookies? Vítejte v ČR

Podnikatel.cz: Babiš: E-shopy z EET možná vyjmeme

Babiš: E-shopy z EET možná vyjmeme

Podnikatel.cz: Babiše přesvědčila 89letá podnikatelka?!

Babiše přesvědčila 89letá podnikatelka?!

Lupa.cz: Google měl výpadek, nejel Gmail ani YouTube

Google měl výpadek, nejel Gmail ani YouTube

Měšec.cz: Air Bank zruší TOP3 garanci a zdražuje kurzy

Air Bank zruší TOP3 garanci a zdražuje kurzy

DigiZone.cz: Rádio Šlágr má licenci pro digi vysílání

Rádio Šlágr má licenci pro digi vysílání

Root.cz: Vypadl Google a rozbilo se toho hodně

Vypadl Google a rozbilo se toho hodně