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ů
1. Projekt Lambda a anonymní funkce v příkladech
V předchozí části seriálu o novinkách připravovaných pro JDK 7 i o vlastnostech, 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.interrupt()), že má být vlákno ukončeno, což ve vlákně vede ke vzniku výjimky typu InterruptedException.
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 InterruptedException 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 InterruptedException 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.currentThread().interrupt();, 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.sort(List list, Comparator c) popř. Arrays.sort(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
V 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.sort(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 ClassCastException. 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 EmployeeComparatorByName, EmployeeComparatorByAge a EmployeeComparatorBySalary vytvoří v metodě run() lokální proměnné typu anonymní funkce, což je typ totožný s Comparator<Employee>. 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.sort(). 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 EmployeeComparatorByAge 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 EmployeeComparatorByAge.class vytvoří soubor SortTest3$1.class, 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
- Java™ Platform, Standard Edition 7 Binary Snapshot Releases
http://dlc.sun.com.edgesuite.net/jdk7/binaries/index.html - Trying the prototype
http://mail.openjdk.java.net/pipermail/lambda-dev/2010-August/002179.html - Better closures (for Java)
http://blogs.sun.com/jrose/entry/better_closures - Lambdas in Java: An In-Depth Analysis
http://www.infoq.com/articles/lambdas-java-analysis - Class ReflectiveOperationException
http://download.java.net/jdk7/docs/api/java/lang/ReflectiveOperationException.html - Proposal: Indexing access syntax for Lists and Maps
http://mail.openjdk.java.net/pipermail/coin-dev/2009-March/001108.html - Proposal: Elvis and Other Null-Safe Operators
http://mail.openjdk.java.net/pipermail/coin-dev/2009-March/000047.html - Java 7 : Oracle pushes a first version of closures
http://www.baptiste-wicht.com/2010/05/oracle-pushes-a-first-version-of-closures/ - Groovy: An agile dynamic language for the Java Platform
http://groovy.codehaus.org/Operators - Better Strategies for Null Handling in Java
http://www.slideshare.net/Stephan.Schmidt/better-strategies-for-null-handling-in-java - Control Flow in the Java Virtual Machine
http://www.artima.com/underthehood/flowP.html - Java Virtual Machine
http://en.wikipedia.org/wiki/Java_virtual_machine - ==, .equals(), compareTo(), and compare()
http://leepoint.net/notes-java/data/expressions/22compareobjects.html - New JDK7 features
http://openjdk.java.net/projects/jdk7/features/ - Project Coin: Bringing it to a Close(able)
http://blogs.sun.com/darcy/entry/project_coin_bring_close - ClosableFinder source code
http://blogs.sun.com/darcy/resource/ProjectCoin/CloseableFinder.java - Joe Darcy blog about JDK
http://blogs.sun.com/darcy - Java 7 – more dynamics
http://www.baptiste-wicht.com/2010/04/java-7-more-dynamics/ - ArrayList (JDK 1.4)
http://download.oracle.com/javase/1.4.2/docs/api/java/util/ArrayList.html