Hlavní navigace

Novinky v JDK 7 (3) + co v JDK 7 naopak nenajdeme

25. 11. 2010
Doba čtení: 19 minut

Sdílet

Ve třetí části seriálu o nových vlastnostech programovacího jazyka Java i jeho virtuálního stroje dokončíme popis syntaktických a sémantických změn, které lze najít v JDK 7. Ve druhé části článku si navíc řekneme, na které změny budeme naopak muset počkat až do vydání JDK 8 (v ideálním případě bude tato verze k dispozici v polovině roku 2012) – kromě usnadnění práce s kolekcemi se například jedná o projekt Lambda, který poměrně zásadním způsobem rozšiřuje sémantiku Javy o anonymní funkce a uzávěry.

Obsah

1. Práce s výjimkami v Javě

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

7. Elvis operátory

8. Projekt Lambda – radikální rozšíření syntaxe a sémantiky Javy

9. Odkazy na Internetu

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 IndexOutOfBou­ndsException (tj. uživatel na příkazové řádce nezadal žádný argument, takže je pole args prázdné) a NumberFormatEx­ception (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.

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í:

  1. Zjednodušená inicializace kolekcí.
  2. Přístup k prvkům kolekcí pomocí „indexového“ operátoru.
  3. Zavedení podpory pro takzvané Elvis operátory.
  4. 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];
}

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:

root_podpora

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

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

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