Obsah
2. Vylepšené zpracování výjimek – motivace
3. Vylepšené zpracování výjimek – demonstrační příklad
4. Co v JDK 7 nakonec nenajdeme?
5. Zjednodušená inicializace kolekcí
6. Přístup k prvkům kolekcí přes „indexové“ závorky
8. Projekt Lambda – radikální rozšíření syntaxe a sémantiky Javy
1. Práce s výjimkami v Javě
V předchozích dvou částech miniseriálu o novinkách, které můžeme najít v nově připravovaném JDK 7 nebo taktéž v OpenJDK 7, jsme si popsali některé změny (přesněji řečeno rozšíření) v syntaxi a sémantice jazyka Java, které byly do Javy přidány v rámci projektu Coin. Kromě možnosti zápisu binárních čísel a číselných konstant s podtržítky (což je skutečně nepatrná a spíše kosmetická změna) se jednalo o rozšíření příkazu switch takovým způsobem, že se v jednotlivých větvích dají používat řetězcové literály, dále o rozšíření bloku try v programové konstrukci try-catch-finally o možnost vytváření (finálních) objektů implementujících rozhraní AutoCloseable, pro něž se automaticky zavolá metoda close() a v neposlední řadě taktéž o zavedení nového operátoru diamant, který lze použít pro zjednodušení deklarace (s generikami) a současně i konstrukce objektů. Ve všech případech se v podstatě jednalo o „syntaktický cukr“, protože stejné funkcionality je možné dosáhnout i v současném JDK 6/OpenJDK 6, i když za cenu delšího a někdy méně čitelného kódu.
I další novinka, která byla v rámci projektu Coin do JDK 7 přidána, byla navržena z toho důvodu, aby se zjednodušil a taktéž zestručnil zápis programů. Jedná se o rozšíření možností bloku catch. V současné verzi JDK lze blok catch zapsat pouze takovým způsobem, že dokáže zachytit jen jeden typ výjimky. Samozřejmě i zde ovšem platí hierarchie tříd, tj. ve skutečnosti se zachytí určitý typ výjimky nebo potomek této výjimky. Po jednom bloku try může následovat libovolný počet bloků catch, z nichž každý musí zachycovat jinou výjimku, popř. obecnější výjimku, než předchozí bloky catch – je tomu tak kvůli tomu, že se jednotlivé bloky catch chovají podle pravidla KDPTDM („kdo dřív přijde, ten dřív mele :-)“), tj. první blok catch, který může výjimku daného typu zachytit, to skutečně udělá. Tato podmínka pro vzájemné řazení bloků catch je testována přímo při překladu programu, takže se například následující zdrojový kód nepodaří přeložit:
import java.io.File; import java.net.URI; public class ExceptionTest1 { public static void main(String[] args) { try { File f = new File(new URI(args[0])); } catch (Exception e) { e.printStackTrace(); } catch (NullPointerException e) { e.printStackTrace(); } catch (IllegalArgumentException e) { e.printStackTrace(); } } }
Při pokusu o překlad se vypíše následující chybové hlášení:
javac ExceptionTest1.java ExceptionTest0.java:16: exception java.lang.NullPointerException has already been caught catch (NullPointerException e) ^ ExceptionTest0.java:20: exception java.lang.IllegalArgumentException has already been caught catch (IllegalArgumentException e) ^ 2 errors
Naproti tomu se nepatrně upravený zdrojový kód přeloží v pořádku, protože obecná výjimka typu Exception je zachycována až v posledním bloku catch a konkrétnější typy výjimek jsou zachyceny před tímto blokem:
import java.io.File; import java.net.URI; public class ExceptionTest2 { public static void main(String[] args) { try { File f = new File(new URI(args[0])); } catch (NullPointerException e) { e.printStackTrace(); } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } } }
Pro zajímavost se můžeme podívat do vygenerovaného bajtkódu na to, jakým způsobem jsou jednotlivé bloky catch přeloženy. V tomto případě se využívají takzvané tabulky výjimek, které pro každý blok catch obsahují rozsah adres, pro který je daný blok platný, dále adresu, na kterou přejde řízení programu ve chvíli, kdy k výjimce dojde a taktéž typ výjimky. Blok try je tedy reprezentován instrukcemi na adresách 0 až 18 a jednotlivé handlery výjimek začínají na adresách 21, 29 a 37 (jednotlivé bloky kódu jsou odděleny ručně přidaným řádkem s pomlčkami):
Compiled from "ExceptionTest2.java" public class Test1 extends java.lang.Object{ public ExceptionTest2(); Code: 0: aload_0 1: invokespecial #1; //Method java/lang/Object."<init>":()V 4: return public static void main(java.lang.String[]); Code: // ---------------------------------------------------------------- 0: new #2; //class java/io/File 3: dup 4: new #3; //class java/net/URI 7: dup 8: aload_0 9: iconst_0 10: aaload 11: invokespecial #4; //Method java/net/URI."<init>":(Ljava/lang/String;)V 14: invokespecial #5; //Method java/io/File."<init>":(Ljava/net/URI;)V 17: astore_1 18: goto 42 // ---------------------------------------------------------------- 21: astore_1 22: aload_1 23: invokevirtual #7; //Method java/lang/NullPointerException.printStackTrace:()V 26: goto 42 // ---------------------------------------------------------------- 29: astore_1 30: aload_1 31: invokevirtual #9; //Method java/lang/IllegalArgumentException.printStackTrace:()V 34: goto 42 // ---------------------------------------------------------------- 37: astore_1 38: aload_1 39: invokevirtual #11; //Method java/lang/Exception.printStackTrace:()V // ---------------------------------------------------------------- 42: return Exception table: from to target type 0 18 21 Class java/lang/NullPointerException 0 18 29 Class java/lang/IllegalArgumentException 0 18 37 Class java/lang/Exception }
2. Vylepšené zpracování výjimek – motivace
Způsob zachycování výjimek implementovaný v JDK 6 i ve všech předchozích verzích JDK s sebou přináší jednu nevýhodu, která se projeví zejména ve chvíli, kdy v bloku try může nastat několik vzájemně zcela odlišných výjimek, na něž se reaguje stejnou nebo podobnou sekvencí příkazů (například se může jednat o příkazy, které výjimku zapíšou do logu, provedou rollback v databázi atd.). Jako příklad – který je kvůli omezení délky opět vyumělkovaný! – si ukažme, jak by se v současné oficiální verzi JDK 6 mohl napsat program provádějící jednoduchý výpočet s číslem, které je programu předáno jako argument na příkazovém řádku. Při spuštění programu může nastat několik stavů, které vyvolají výjimku: argument není vůbec zadán, tj. pole args je prázdné; argument je sice zadán, ale neobsahuje celé číslo (nepovede se jeho parsing) a taktéž se může jednat o platné číslo nula, které však vyvolá při dělení výjimku (primitivní datový typ int nemůže, na rozdíl od double, obsahovat konstanty typu Infinity, -Infinity, NaN atd.). Při zpracování výjimky se vypíše chybové hlášení na chybový výstup a současně se textová reprezentace výjimky uloží do jednoduchého logu, který je před koncem běhu programu vypsán na standardní výstup:
import java.util.List; import java.util.ArrayList; public class ExceptionTest3 { public static List<String> log = new ArrayList<String>(); public static void main(String[] args) { try { int i = Integer.parseInt(args[0]); System.out.format("1000 / %d = %d\n", i, 1000/i); } catch (IndexOutOfBoundsException e) { System.err.println("Chyba - jako prvni argument programu musi byt zadano cele cislo"); log.add(e.toString()); } catch (NumberFormatException e) { System.err.println("Chyba - jako prvni argument programu musi byt zadano cele cislo"); log.add(e.toString()); } catch (ArithmeticException e) { System.err.println("Deleni nulou!"); log.add(e.toString()); } finally { System.out.println("\nError log: "); for (String str : log) { System.out.println(str); } } } }
Chování výše uvedeného programu si můžeme jednoduše otestovat:
$ java ExceptionTest3 Chyba - jako prvni argument programu musi byt zadano cele cislo Error log: java.lang.ArrayIndexOutOfBoundsException: 0
$ java ExceptionTest3 hola Chyba - jako prvni argument programu musi byt zadano cele cislo Error log: java.lang.NumberFormatException: For input string: "hola"
$ java ExceptionTest3 0 Deleni nulou! Error log: java.lang.ArithmeticException: / by zero
$ java ExceptionTest3 42 1000 / 42 = 23 Error log:
3. Vylepšení zpracování výjimek – demonstrační příklad
Při pohledu na demonstrační příklad uvedený v předchozí kapitole můžeme zjistit, že se v něm dvakrát opakuje stejná sekvence příkazů – jedná se o příkazy zapsané v blocích catch, které zachycují výjimky typu IndexOutOfBoundsException (tj. uživatel na příkazové řádce nezadal žádný argument, takže je pole args prázdné) a NumberFormatException (zadaný argument není parsovatelný na celé číslo). V současné (přesněji řečeno oficiální) verzi JDK 6 lze tuto duplikaci vyřešit například zachycením nějaké obecnější výjimky. Ovšem to s sebou přináší několik problémů; především to, že po přidání dalších příkazů do bloku try může dojít k tomu, že obecný handler v bloku catch bude nekorektně obsluhovat (a především zachytávat) i ty výjimky, s kterými autor původního kódu nepočítal – a překladač na tuto skutečnost samozřejmě nemusí upozornit, zejména v případech, kdy se kvůli snaze o zjednodušení programu zachycují obecné výjimky typu Exception nebo RuntimeException.
V JDK 7 je možné tento problém vyřešit s využitím upraveného bloku catch, v němž je použit přetížený operátor |, pomocí něhož lze blok catch využít pro několik typů (i navzájem nesouvisejících) výjimek. Syntaxe nového způsobu zápisu je následující (povšimněte si především v tomto případě povinného modifikátoru final):
catch (final ExceptionType1 | ExceptionType2 | ... ExceptionTypeN ex) { }
S využitím výše uvedené nové syntaxe bloku catch lze demonstrační příklad z předchozí kapitoly přepsat následovně (navíc je ještě provedeno jedno malé zjednodušení – použití operátoru diamant při vytváření seznamu):
import java.util.List; import java.util.ArrayList; public class ExceptionTest4 { public static List<String> log = new ArrayList<>(); public static void main(String[] args) { try { int i = Integer.parseInt(args[0]); System.out.format("1000 / %d = %d\n", i, 1000/i); } catch (final IndexOutOfBoundsException | NumberFormatException e) { System.err.println("Chyba - jako prvni argument programu musi byt zadano cele cislo"); log.add(e.toString()); } catch (ArithmeticException e) { System.err.println("Deleni nulou!"); log.add(e.toString()); } finally { System.out.println("\nError log: "); for (String str : log) { System.out.println(str); } } } }
Upravený příklad pracuje stejně jako jeho předchozí varianta:
$ ./java ExceptionTest4 Chyba - jako prvni argument programu musi byt zadano cele cislo Error log: java.lang.ArrayIndexOutOfBoundsException: 0
$ ./java ExceptionTest4 hej Chyba - jako prvni argument programu musi byt zadano cele cislo Error log: java.lang.NumberFormatException: For input string: "hej"
$ ./java ExceptionTest4 0 Deleni nulou! Error log: java.lang.ArithmeticException: / by zero
$ ./java ExceptionTest4 8 1000 / 8 = 125 Error log:
Pro zajímavost se opět můžeme podívat na to, jak se tento příklad přeloží do bajtkódu. Za povšimnutí stojí především tabulka se seznamem zachytávaných výjimek – ta nyní obsahuje sedm položek. První tři položky odpovídají dvojici bloků catch explicitně zapsaných programátorem (blok catch s přetíženým operátorem | je reprezentován dvojicí položek). Další čtyři položky jsou určeny pro zachycení obecných výjimek jak ve vlastním bloku try, tak i ve všech blocích catch. Je tomu tak z toho důvodu, aby se vždy provedl blok finally (pokud by však blok finally nebyl uveden, obsahovala by tabulka pouze první tři položky, což si můžete jednoduše vyzkoušet po překladu zdrojového kódu do souboru .class následovaného zpětným překladem pomocí nástroje javapc):
Compiled from "ExceptionTest4.java" public class ExceptionTest4 extends java.lang.Object { public static java.util.List<java.lang.String> log; public ExceptionTest4(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return public static void main(java.lang.String[]); Code: // ---------------------------------------------------------------- // tělo bloku try // ---------------------------------------------------------------- 0: aload_0 1: iconst_0 2: aaload 3: invokestatic #2 // Method java/lang/Integer.parseInt:(Ljava/lang/String;)I 6: istore_1 7: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream; 10: ldc #4 // String 1000 / %d = %d\n 12: iconst_2 13: anewarray #5 // class java/lang/Object 16: dup 17: iconst_0 18: iload_1 19: invokestatic #6 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer; 22: aastore 23: dup 24: iconst_1 25: sipush 1000 28: iload_1 29: idiv 30: invokestatic #6 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer; 33: aastore 34: invokevirtual #7 // Method java/io/PrintStream.format:(Ljava/lang/String;[Ljava/lang/Object;)Ljava/io/PrintStream; 37: pop 38: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream; // ---------------------------------------------------------------- // blok finally volaný tehdy, když nenastane žádná nezachytitelná výjimka // ---------------------------------------------------------------- 41: ldc #8 // String \nError log: 43: invokevirtual #9 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 46: getstatic #10 // Field log:Ljava/util/List; 49: invokeinterface #11, 1 // InterfaceMethod java/util/List.iterator:()Ljava/util/Iterator; 54: astore_1 55: aload_1 56: invokeinterface #12, 1 // InterfaceMethod java/util/Iterator.hasNext:()Z 61: ifeq 84 64: aload_1 65: invokeinterface #13, 1 // InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object; 70: checkcast #14 // class java/lang/String 73: astore_2 74: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream; 77: aload_2 78: invokevirtual #9 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 81: goto 55 84: goto 283 // ---------------------------------------------------------------- 87: astore_1 88: getstatic #17 // Field java/lang/System.err:Ljava/io/PrintStream; 91: ldc #18 // String Chyba - jako prvni argument programu musi byt zadano cele cislo 93: invokevirtual #9 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 96: getstatic #10 // Field log:Ljava/util/List; 99: aload_1 100: invokevirtual #19 // Method java/lang/RuntimeException.toString:()Ljava/lang/String; 103: invokeinterface #20, 2 // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z 108: pop 109: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream; // ---------------------------------------------------------------- 112: ldc #8 // String \nError log: 114: invokevirtual #9 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 117: getstatic #10 // Field log:Ljava/util/List; 120: invokeinterface #11, 1 // InterfaceMethod java/util/List.iterator:()Ljava/util/Iterator; 125: astore_1 126: aload_1 127: invokeinterface #12, 1 // InterfaceMethod java/util/Iterator.hasNext:()Z 132: ifeq 155 135: aload_1 136: invokeinterface #13, 1 // InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object; 141: checkcast #14 // class java/lang/String 144: astore_2 145: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream; 148: aload_2 149: invokevirtual #9 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 152: goto 126 155: goto 283 // ---------------------------------------------------------------- 158: astore_1 159: getstatic #17 // Field java/lang/System.err:Ljava/io/PrintStream; 162: ldc #22 // String Deleni nulou! 164: invokevirtual #9 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 167: getstatic #10 // Field log:Ljava/util/List; 170: aload_1 171: invokevirtual #23 // Method java/lang/ArithmeticException.toString:()Ljava/lang/String; 174: invokeinterface #20, 2 // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z 179: pop 180: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream; 183: ldc #8 // String \nError log: 185: invokevirtual #9 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 188: getstatic #10 // Field log:Ljava/util/List; 191: invokeinterface #11, 1 // InterfaceMethod java/util/List.iterator:()Ljava/util/Iterator; 196: astore_1 197: aload_1 198: invokeinterface #12, 1 // InterfaceMethod java/util/Iterator.hasNext:()Z 203: ifeq 226 206: aload_1 207: invokeinterface #13, 1 // InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object; 212: checkcast #14 // class java/lang/String 215: astore_2 216: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream; 219: aload_2 220: invokevirtual #9 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 223: goto 197 226: goto 283 // ---------------------------------------------------------------- // blok finally volaný tehdy, když nastane jiná výjimka // ---------------------------------------------------------------- 229: astore_3 230: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream; 233: ldc #8 // String \nError log: 235: invokevirtual #9 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 238: getstatic #10 // Field log:Ljava/util/List; 241: invokeinterface #11, 1 // InterfaceMethod java/util/List.iterator:()Ljava/util/Iterator; 246: astore 4 248: aload 4 250: invokeinterface #12, 1 // InterfaceMethod java/util/Iterator.hasNext:()Z 255: ifeq 281 258: aload 4 260: invokeinterface #13, 1 // InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object; 265: checkcast #14 // class java/lang/String 268: astore 5 270: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream; 273: aload 5 275: invokevirtual #9 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 278: goto 248 281: aload_3 282: athrow // ---------------------------------------------------------------- 283: return Exception table: from to target type 0 38 87 Class java/lang/IndexOutOfBoundsException 0 38 87 Class java/lang/NumberFormatException 0 38 158 Class java/lang/ArithmeticException 0 38 229 any 87 109 229 any 158 180 229 any 229 230 229 any static {}; Code: 0: new #24 // class java/util/ArrayList 3: dup 4: invokespecial #25 // Method java/util/ArrayList."<init>":()V 7: putstatic #10 // Field log:Ljava/util/List; 10: return }
4. Co v JDK 7 nakonec nenajdeme?
Již v první části tohoto miniseriálu jsme si řekli, že některá vylepšení Javy, která se původně měla objevit již v JDK 7, byla po přijetí plánu „B“ přesunuta až do JDK 8 popř. do JDK 9. U některých nových vlastností však není zřejmé, ve kterých budoucích verzích JDK se skutečně tyto vlastnosti objeví. V následujícím textu si některá z těchto vlastností popíšeme, protože s určitou pravděpodobností budou s předstihem implementována v OpenJDK 7 (ostatně i dnes je možné si přeložit například OpenJDK 7 i s dále zmíněným projektem Lambda). Jedná se o následující rozšíření:
- Zjednodušená inicializace kolekcí.
- Přístup k prvkům kolekcí pomocí „indexového“ operátoru.
- Zavedení podpory pro takzvané Elvis operátory.
- Zavedení (resp. rozšíření) podpory pro anonymní funkce a uzávěry.
5. Zjednodušená inicializace kolekcí
Jednou z novinek plánovaných v rámci projektu Coin, které však nakonec v JDK 7 neuvidíme, je zjednodušení inicializace kolekcí. Jedná se především o odstranění nutnosti používat metody typu put() a add() při deklaraci kolekce následované ihned její konstrukcí a naplněním daty (prvky). Konstrukci a současné naplnění kolekce v oficiálním JDK 6 není možné zapsat pomocí jednoho příkazu (jen nešikovně pomocí Arrays.asList() apod.), na rozdíl od polí. Pole je totiž možné deklarovat, vytvořit a současně i naplnit, popř. lze pouze vytvořit a ihned naplnit anonymní pole:
public class ArrayTest { int[] pole1 = new int[] {1,2,3,4}; int[][] pole2 = new int[][] { {1,2,3}, {4,5,6}, {7,8,9} }; String[] pole3 = new String[] {"aaa", "bbb", "ccc"}; // výběr druhého prvku z anonymního pole int x = new int[] {1,2,3,4}[1]; }
V JDK 8 bude (doufejme) možné podobnou konstrukci použít i pro kolekce, nejenom pro pole. Pro seznamy byla zvolena konstrukce obsahující hranaté závorky:
List<Integer> numbers = [ 1, 2, 4, 8, 16, 32, 64, 128 ];
Povšimněte si absence new.
List<String> strings = [ "www", "root", "cz" ];
Pro množiny byla naproti tomu zvolena konstrukce se složenými závorkami:
Set<Integer> numbers = { 256, 512, 1024, 2048, 4096 };
Set<String> strings = { "www", "java", "net" };
Čtenářům velmi pravděpodobně dobře známý Joshua Bloch uvádí velmi pěkný příklad (kupodivu nežije v ČR):
Set<Senator> honestSenators = {};
Pro mapy (asociativní pole) se používá konstrukce, která se nápadně podobná syntaxi známé z některých skriptovacích jazyků:
Map<String, String> translations = { "Hi" : "Bonjour", "Goodbye" : "Au revoir", "Thanks" : "Merci" };
S velkou pravděpodobností bude umožněna i tvorba složitějších datových struktur (opět se jedná o příklad převzatý od Joshuy Blocha):
List<List<Integer>> pascalsTriangle = [ [1], [1, 1], [1, 2, 1], [1, 3, 3, 1], [1, 4, 6, 4, 1] ];
Všechny výše zmíněné konstrukce bude samozřejmě možné použít jak při inicializaci atributů objektů nebo tříd, tak i při inicializaci lokálních proměnných, podobně jako je tomu u inicializace „jednoduchých“ objektů, primitivních datových typů i polí.
6. Přístup k prvkům kolekcí přes „indexové“ závorky
S inicializacemi kolekcí souvisí i další nová vlastnost, která by se mohla objevit v JDK 8. Jedná se o možnost přistupovat k prvkům kolekcí, přesněji řečeno k seznamům a mapám (asociativním polím), nejenom s využitím metod get(), set() a put(), ale též pomocí hranatých „indexových“ závorek, tedy podobným způsobem, jakým se přistupuje k prvkům polí. U seznamů se používá stejná syntaxe jako u polí, tj. uvnitř hranatých závorek se musí nacházet celočíselná konstanta nebo výraz, jenž se vyhodnocuje na celé číslo. Identifikátor seznamu následovaný hranatými závorkami s indexem lze použít jak na levé straně přiřazovacího příkazu (zde nahrazuje metodu set()), tak i jako součást libovolného výrazu (zde nahrazuje metodu get()). Následuje jednoduchá ukázka, z které je zřejmé, že s využitím nové syntaxe může dojít ke zjednodušení zápisu některých algoritmů:
public class CollectionTest2 { public static void main(String[] args) { List<String> list = ["aaa", "bbb", "ccc"]; String firstElement = list[0]; // list.get(0); list[2] = "nova hodnota"; // list.set(2, "nova hodnota"); } }
Podobný způsob zápisu bude možné použít i pro mapy (asociativní pole), ovšem s tím rozdílem, že se v tomto případě nahrazují metody get() a put(). Navíc se jako „indexy“ nepoužívají pouze celá čísla, ale klíče, jejichž typy odpovídají typům klíčů dané mapy:
public class CollectionTest2 { public static void main(String[] args) { Map<String, String> map = new HashMap<Integer, String>(4); map["Klic"] = "Hodnota"; // map.put("Klic", "Hodnota"); System.out.println(map["Jiny klic"]); // map.get("Jiny klic"); } }
7. Elvis operátory
Dalším rozšířením syntaxe a sémantiky Javy, na jehož konečnou implementaci si budeme muset ještě několik měsíců počkat, je zavedení takzvaných Elvis operátorů (nenašel jsem nikde přesné vysvětlení, proč mají takový název ani jaký je správný český ekvivalent názvu). Tyto typy operátorů nejsou v žádném případě ve světě programovacích jazyků nové, můžeme je nalézt například v jazyku Groovy a pod jiným označením i v C#. V JDK 8 by měla být množina již zavedených operátorů rozšířena o další trojici: binární (nikoli ternární!) operátor ?:, dále o operátor ?. a konečně o operátor ?[]. Všechny tři nové operátory mají zjednodušit práci s objekty, které mohou nabývat hodnoty null. V současné Javě se v programech často používají (někdy kvůli špatně navrženému API!) podmínky typu if (xxx != null), xxx!=null ? xxx.doIt() : pass(), popř. se odchytává výjimka typu NPE – Null Pointer Exception. Trojice nových operátorů může tyto programy zjednodušit.
Binární operátor ?: nejprve zjistí, zda je objekt na jeho levé straně roven null. Pokud tomu tak skutečně je, vrátí se vyhodnocená pravá strana výrazu. V opačném případě, tj. pokud je objekt na levé straně rozdílný od null, je tento objekt vrácen bez ohledu na pravou stranu výrazu (ta se zcela ignoruje). Použití tohoto operátoru je jednodušší než jeho popis:
displayName = user.name ?: "Anonymous";
Popř. poněkud složitější použití (nikdy nedojde k NPE):
Integer ival = ...; // může být klidně i null int i = ival ?: -1; // provádí se unboxing, ale nedojde k NPE
Druhý operátor ?. pracuje podobně jako stávající operátor . (tečka), ovšem pravá strana (metoda, atribut…) se zpracuje pouze tehdy, pokud je objekt na levé straně tohoto operátoru rozdílný od null. Tyto operátory lze samozřejmě „zřetězit“, tj. lze například přistupovat k atributům atributů, a to bez nebezpečí, že by došlo k vyvolání NPE:
object?.callMethod(); streetName = user?.address?.street; String s = mayBeNull?.toString() ?: "null";
Poslední z nových operátorů se zapisuje pomocí znaků ?[], takže již čtenáři pravděpodobně uhodli, že se jedná o operátor indexace polí (a pravděpodobně i kolekcí – viz předcházející kapitolu!), který ovšem přistupuje k prvkům pole (kolekce) pouze v případě, že daný objekt typu pole (kolekce) nemá hodnotu null:
members?[2]; // nevyhodí NPE ani když members==null
Složitější příklad:
String aMember = null; if (g != null && g.members != null && g.members[0].name != null) { aMember = g.members[0].name; } else { aMember = "nobody"; }
bude možné nahradit jednoduchým a mnohem přehlednějším one-linerem:
String aMember = g?.members?[0]?.name ?: "nobody";
8. Projekt Lambda – radikální rozšíření syntaxe a sémantiky Javy
Posledním rozšířením syntaxe a sémantiky Javy, které však nakonec nebylo do JDK 7 zařazeno, je podpora pro tvorbu anonymních funkcí, datového typu funkce a uzávěrů (closure(s)). Tyto vlastnosti jsou již z velké části implementovány v rámci projektu Lambda. Zatímco všechny předchozí syntaktické novinky byly vlastně pouze evoluční, je zavedení anonymních funkcí a uzávěrů spíše revoluce, alespoň na poli Javy (a trošku se tím potvrzuje známé motto, že všechny jazyky postupem času dospějí k LISPu :-). Anonymní funkce (též lambda výrazy) jsou však v Javě zvláštní tím, že jsou staticky typované, zatímco klasické lambda výrazy známe spíše z dynamicky typovaných programovacích jazyků.
Zatímco sémantika lambda výrazů je celkem zřejmá (podrobněji se tímto tématem budeme zabývat příště), syntaxe, která byla zvolena pro Javu, vyvolává mezi uživateli poměrně velké polemiky: samotný symbol lambdy je nahrazen křížkem (což je jeden z mála volných tisknutelných symbolů v ASCII), celé tělo je od parametrů odděleno pomocí kulatých závorek (jiné jazyky používají například čitelnější zápis =>) a při volání lambda výrazu se musí použít tečka před závorkami s parametry, což souvisí s tím, že Java má z historických důvodů rozdělený jmenný prostor pro funkce a atributy/proměnné (a nebylo by tedy zřejmé, zda se má zavolat lambda výraz přiřazený do atributu nebo stejně pojmenovaná metoda!). Projektem Lambda se budeme podrobněji zabývat příště, zde si pouze uvedeme několik příkladů:
// lambda výraz, pouze zapsaný a nikam nepřiřazený, nevyhodnocovaný #(int x) (x+1);
// přiřazení lambda výrazu do proměnné či atributu // (povšimněte si typu této proměnné/atributu) #int(int) inc = #(int x) (x+1);
// vyhodnocení (s tečkou před závorkou s parametry!) int y = inc.(42)
9. Odkazy na Internetu
- 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