Novinky v JDK 7 aneb mírný pokrok v mezích zákona (1)

Pavel Tišnovský 11. 11. 2010

V tomto článku si popíšeme některé z nových vlastností programovacího jazyka Java i jeho virtuálního stroje, které jsou zařazeny do chystané verze JDK 7. Nebude se jednat pouze o stručný popis vlastností, protože se kromě zmínky o nových knihovnách, třídách a rozhraních budeme věnovat i rozšíření syntaxe a sémantiky Javy, a to s ohledem na to, jak se nová syntaxe/sémantika projevuje v generovaném bajtkódu.

Obsah

1. Novinky v JDK 7 aneb mírný pokrok v mezích zákona

2. Plán „A“ a plán „B“

3. Projekt Coin – rozšíření syntaxe a sémantiky Javy

4. Nové možnosti při deklaraci numerických konstant

5. Příkaz switch v JDK 1.0 až JDK 5

6. Rychlý pohled „pod kapotu“ JDK

7. Použití řetězců v příkazu switch

8. Struktura generovaného bajtkódu při použití řetězců v příkazu switch

9. Odkazy na Internetu

1. Novinky v JDK 7 aneb mírný pokrok v mezích zákona

Specifikace programovacího jazyka Java i jeho virtuálního stroje se postupně vyvíjí společně s tím, jak se rozšiřují jak možnosti počítačů a dalších programovacích jazyků, tak i způsoby nasazení Javy v praxi. V roce 1996 firma Sun Microsystems zveřejnila první verzi JDK 1.0 a ve verzích následujících docházelo k postupnému rozšiřování a vylepšování syntaxe a sémantiky tohoto programovacího jazyka, což se v některých případech projevilo i na nutnosti změny generovaného bajtkódu. Z vlastností jazyka, které byly postupně do Javy přidány, můžeme jmenovat například vnitřní třídy a reflexi (JDK 1.1), kolekce (J2SE 1.2), klíčové slovo assert (J2SE 1.4), generické typy, anotace, autoboxing, výčtový typ, smyčku typu for-each a variabilní počet parametrů metod (J2SE 5.0). Zatímco v následující verzi Java SE 6 se mnoho změn neobjevilo, jinak je tomu u specifikace Java SE 7 implementované v JDK 7 a OpenJDK7. Při rozhodování o tom, jaké nové vlastnosti by se měly v JDK 7 projevit, byly do úvahy brány mj. i kritéria popsaná v následujícím tex­tu.

Hlavní prioritou při posuzování vhodnosti či nevhodnosti jednotlivých změn byla jasnost a srozumitelnost zdrojových kódů vytvářených v Javě. To je na první pohled rozumné kritérium, ovšem některé užitečné, ale na první pohled ne zcela jasné nebo jen pro javisty neobvyklé konstrukce, nakonec nebyly do Javy zařazeny. Například i přes existenci autoboxingu (JDK 5) stále nelze zapsat 1.toString(), ale jen ((Integer)1).toS­tring(), new Integer(1).toS­tring() nebo lépe Integer.value­Of(1).toStrin­g(). Druhým kritériem braným v úvahu bylo, že čtení přehledného kódu je důležitější než jeho snadný a úsporný zápis, což mj. znamená, že Java je poměrně „ukecaný“ jazyk: například settery a gettery je – alespoň prozatím – nutné psát explicitně namísto použití dekorátorů či anotací; podmínku, zda je nějaká reference rovna null, je nutné taktéž psát explicitně atd. Jiné programovací jazyky mají i odlišné priority a jsou směrovány k jiným typům aplikací. Dobrým příkladem může být Perl s mnoha „magickými“ proměnnými a úsporným zápisem.

2. Plán „A“ a plán „B“

Původní návrhy pro nové vlastnosti JDK 7 byly vytvořeny ještě ve firmě Sun Microsystems. Mělo se jednat o doslova revoluční verzi JDK, v níž se měly do programovacího jazyka Java dostat konstrukce známé spíše z funkcionálních programovacích jazyků (například lambda výrazy, ovšem typované!); z JVM se měla stát universální platforma pro mnoho dalších, většinou dynamicky typovaných programovacích jazyků atd. Ovšem celý vývoj JDK 7 se dostal do časového skluzu, což je ostatně už u mnoha velkých projektů spíše pravidlem než výjimkou ;-). Navíc mezitím firma Sun otevřela cca 95% svého JDK pod názvem OpenJDK a o nějaký čas později došlo k převzetí firmy Sun Microsystems firmou Oracle, takže se poněkud změnily priority vývoje (nejenom) JDK 7. Výsledkem všech těchto skutečností bylo sestavení dvou plánů: plánu „A“ a plánu „B“, s tím, že se po zralé úvaze rozhodne, který z plánů bude uskutečněn.

V plánu „A“ se počítalo s tím, že se vydá „revoluční“ JDK 7 s velkým množstvím změn, ovšem až v roce 2012, což je velký časový skluz. Přednosti tohoto plánu spočívají v tom, že se počet dostupných verzí podporovaných JDK nebude příliš zvyšovat, ovšem velkou nevýhodou je již zmíněný časový skluz. Naproti tomu se v plánu „B“ vývoj JDK 7 rozdělil na JDK 7, které má být dokončené již v polovině příštího roku a na JDK 8, které by snad mohlo být k dispozici na konci roku 2012. Předností tohoto plánu je to, že vývojáři budou moci již příští rok využít některé z nových (již implementovaných a otestovaných) vlastností JDK 7, ovšem nevýhoda spočívá ve větší fragmentaci JDK a jejich podpory: zatímco dnes se stále ještě poměrně často používá verze 1.4.2 a samozřejmě též 5.0 a 6.0, bylo by to v roce 2012 hned pět různých verzí JDK (podpora pro 1.4.2 a 5.0 sice teoreticky vyprší, ale mnoho firem nemůže z různých důvodů na novou verzi ihned přejít). Dá se tedy počítat s tím, že JDK 7 nebude přijato všemi vývojáři, protože si mnozí raději počkají až na JDK 8.

3. Projekt Coin – rozšíření syntaxe a sémantiky Javy

Na letošní konferenci JavaOne bylo prezentováno rozhodnutí o dalším vývoji JDK 7 – nakonec zvítězil plán „B“. Nové vlastnosti JDK tedy byly rozděleny do dvou množin, z nichž první bude (v podstatě již je) implementována v JDK 7 a druhá až v JDK 8 o dva roky později. V novinkách, které budou v JDK 7, je zahrnuta například podpora pro dynamicky typované jazyky běžící v JVM (nová instrukce v bajtkódu), část projektu Coin (změna syntaxe a sémantiky jazyka), rozšíření NIO.2, podpora XRender Graphics Pipeline operacemi implementovanými v Java 2D (s čímž souvisí i nárůst výkonu některých grafických operací, což si řekneme příště), projekt Nimbus pro Swingové aplikace (taktéž bude popsáno příště), zahrnutí nových verzí TLS 1.2 a JDBC 4.1, podpora pro Unicode 6.0 atd. Pro běžné programátory budou asi nejviditelnější změny, které jsou implementovány v rámci výše zmíněného projektu Coin.

Jedná se o projekt, který do programovacího jazyka Java vnáší malé, ale o to příjemnější změny v syntaxi a sémantice. V některých případech se jedná o pouhý „syntaktický cukr“, ale na další vlastnosti čekali programátoři mnohdy i několik let. Zajímavé je, že i přes rozšíření syntaxe jazyka Java nebylo nutné v rámci projektu Coin přidat do tohoto programovacího jazyka žádné nové klíčové slovo a taktéž se nemusel rozšiřovat bajtkód o další instrukce. Mezi nové prvky Javy, které projekt Coin přináší již do JDK 7 (v JDK 8 je těchto prvků mnohem více), patří především:

  • Deklarace celočíselných binárních konstant
  • Použití podtržítka pro lepší čitelnost numerických konstant
  • Příkaz switch a řetězce
  • „Diamant“ – zjednodušený zápis deklarací při použití generik
  • Automatic Resource Management (ARM) – automatické volání metody close v rámci rozšířeného bloku try
  • Vylepšené zpracování výjimek

První tři rozšíření budou popsána v dalších kapitolách, zbylé tři rozšíření až v navazujícím článku.

4. Nové možnosti při deklaraci numerických konstant

Mezi jedno z nejjednodušších rozšíření, které již byly v rámci projektu Coin implementovány, patří podpora pro zápis binárních numerických konstant, tj. čísel ve dvojkové soustavě. Java totiž převzala z programovacího jazyka C a C++ způsob zápisu konstant v osmičkové a šestnáctkové soustavě, ale doposud neexistovala možnost použití soustavy dvojkové (to se hodí například při nízkoúrovňové práci s bitovými příznaky a nově také například při zápisu masek v IPv6). Syntaxe je velmi jednoduchá – čísla v osmičkové soustavě začínají na 0, v soustavě šestnáctkové na a v soustavě binární, jak již asi čtenář uhodl, na 0b. Nově je taktéž možné, aby se při zápisu celých i reálných čísel, bez ohledu na použitý základ číselné soustavy, mohly jednotlivé řády oddělovat podtržítkem. To, kde a zda vůbec bude podtržítko použito, záleží pouze na vývojáři (typicky se asi budou oddělovat miliony a tisíce). Obě popsaná rozšíření Javy, které se mimochodem nijak neprojeví na generovaném bajtkódu, si můžete s (Open)JDK7 snadno vyzkoušet:

public class NumericConstants
{
    public static void main(String[] args)
    {
        int  decimal1 = 1234567890;
        int  decimal2 = 1_234_567_890; // oddělení tisíců, milionů a miliard
        long decimal3 = -99_88_77_66_55_44_33_22_11L;

        // nová možnost - binární čísla
        int  binary1 = 0b01000010;
        long binary2 = 0b0000_0001_0010_0011_0100_0101;

        // poněkud neobvyklé ale možné - záporné binární hodnoty
        long binary3 = -0b0000_0001_0010_0011_0100_0101;

        int  octal1 = 01234567;
        int  octal2 = 012_34_56_77;
        long octal3 = 0666_555_444_333_222_111L;

        int  hexadecimal1 = 0x12345678;
        int  hexadecimal2 = 0x12_34_56_78;
        long hexadecimal3 = 0x88_77_66_55_44_33_22_11L;

        // podtržítka lze použít i při zápisu reálných konstant
        float f1 = -1_2.3_4f;
        double f2 = -1_2.3_4e10;
        double f3 = -1_2.3_4e1_2_3;

        System.out.println(decimal1);
        System.out.println(decimal2);
        System.out.println(decimal3);
        System.out.println();

        System.out.format("%x\n", binary1);
        System.out.format("%x\n", binary2);
        System.out.format("%x\n", binary3);
        System.out.println();

        System.out.format("%o\n", octal1);
        System.out.format("%o\n", octal2);
        System.out.format("%o\n", octal3);
        System.out.println();

        System.out.format("%x\n", hexadecimal1);
        System.out.format("%x\n", hexadecimal2);
        System.out.format("%x\n", hexadecimal3);
        System.out.println();

        System.out.println(f1);
        System.out.println(f2);
        System.out.println(f3);
    }
}

5. Příkaz switch v JDK 1.0 až JDK 5

Mezi množinu několika rozšíření syntaxe i sémantiky Javy, které lze v nové verzi JDK 7 použít, patří i možnost použití řetězců v řídicím příkazu switch. Tato řídicí struktura je v Javě dostupná od samého počátku, tj. od JDK 1.0 (což je vlastně samozřejmé, neboť byla po mírných úpravách převzata z jazyků C a C++). Ovšem původně se jednalo o řídicí strukturu, v níž bylo možné za klíčovým slovem switch použít pouze celočíselný výraz a jednotlivé větve specifikované klíčovým slovem case mohly obsahovat pouze celočíselnou konstantu typu byte, short či int (nikoli však long, což je omezení dané strukturou bajtkódu!). Kromě těchto tří datových typů bylo možné použít i znakový literál, který je v Javě taktéž považován za jinak zapsanou celočíselnou šestnáctibitovou konstantu typu char. Při překladu zdrojového kódu je programová konstrukce vytvořená s využitím klíčových slov switch, case, break a default přeložena do bajtkódu, v němž jsou použity instrukce tableswitch, popř. lookupswit­ch, s nimiž se setkáme i v dalším textu.

Konkrétní výběr jedné z těchto instrukcí, jež se ve vygenerovaném bajtkódu skutečně použije, je proveden v závislosti na tom, která instrukce je pro daný příkaz switch vykonána efektivněji nebo která je reprezentována kratším bajtkódem (to záleží především na tom, jaké konstanty jsou uvedeny u jednotlivých větví case). V J2SE 5.0 byly možnosti řídicí konstrukce switch ve dvou směrech rozšířeny. Kromě primitivních datových typů byte, short, int a char byla přidána i podpora pro instance obalových tříd těchto typů, tj. pro instance tříd Character, Byte, Short a Integer . Tato podpora samozřejmě souvisí s autoboxingem a unboxingem, což jsou taktéž nové vlastnosti J2SE 5.0. Ovšem mnohem užitečnější bylo rozšíření příkazu switch o výčtový typ (enumeration – enum), díky čemuž bylo možné mnoho algoritmů zapsat čitelnějším a taktéž bezpečnějším způsobem, protože se z programů mohly odstranit typově nebezpečné „magické konstanty“.

6. Rychlý pohled „pod kapotu“ JDK

Zajímavé je, že syntaktické a sémantické rozšíření řídicí konstrukce switch o možnost použití výčtového typu vůbec nevedlo k nutnosti změn v bajtkódu, tj. do bajtkódu nebylo nutné přidávat žádné další typy instrukcí. Před provedením rozeskoku s využitím instrukce tableswitch nebo lookupswitch se totiž interně pro objekt výčtového typu zavolá metoda Enum.ordinal(), která vrátí ordinální (pořadové) číslo daného prvku výčtového typu. Získání tohoto celého čísla je velmi snadné, protože instance třídy odvozené od Enum jsou v celém virtuálním stroji unikátní – jedná se o jedináčky neboli singletony. Tento přístup návrhářů Javy ke všem rozšířením jazyka, který je jasně patrný i ze změn provedených v JDK 7, si můžeme ukázat na jednoduchém příkladu. Bude nás zajímat, jakým způsobem se přeloží programová konstrukce switch použitá v následujícím zdrojovém kódu:

enum T {JEDNA, DVA, TRI}

public class EnumSwitchTest
{
    public static void main(String args[])
    {
        T x=T.DVA;
        int y;
        switch (x)
        {
            case JEDNA:
                y=1;
                break;
            case DVA:
                y=2;
                break;
            case TRI:
                y=3;
                break;
            default:
                y=0;
                break;
        }
        System.out.println(y);
    }
}

Pomocným programem javap zavolaným s parametrem -c lze získat bajtkód odpovídající předchozí konstrukci (české poznámky jsem samozřejmě dopsal ručně, podobně jako jsem přidal odřádkování):

public static void main(java.lang.String[]);
  Code:

   0:   getstatic       #2; //Field T.DVA:LT;
   3:   astore_1
   4:   getstatic       #3; //Field Test$1.$SwitchMap$T:[I
   7:   aload_1
   8:   invokevirtual   #4; // zde se volá metoda T.ordinal() vracející číslo 1-3
   11:  iaload
   12:  tableswitch{        // rozhodovací tabulka pro hodnoty 1 až 3
                1: 40;      // skok na instrukci s adresou 40
                2: 45;      // skok na instrukci s adresou 45
                3: 50;      // skok na instrukci s adresou 50
                default: 55 } // skok na instrukci s adresou 55

   40:  iconst_1            // cíl prvního skoku
   41:  istore_2            // uložení celočíselné konstanty 1 do lokální proměnné y
   42:  goto    57          // odpovídá break, tj. ukončení switch

   45:  iconst_2            // cíl druhého skoku
   46:  istore_2            // uložení celočíselné konstanty 2 do lokální proměnné y
   47:  goto    57          // odpovídá break, tj. ukončení switch

   50:  iconst_3            // cíl třetího skoku
   51:  istore_2            // uložení celočíselné konstanty 3 do lokální proměnné y
   52:  goto    57          // odpovídá break, tj. ukončení switch

   55:  iconst_0            // cíl posledního skoku (default)
   56:  istore_2            // uložení celočíselné konstanty 0 do lokální proměnné y
                            // (skok již není zapotřebí, jsme již na adrese 57)

   57:  getstatic       #5; //Field java/lang/System.out:Ljava/io/PrintStream;
   60:  iload_2             // načtení hodnoty lokální proměnné y do zásobníku
   61:  invokevirtual   #6; //Method java/io/PrintStream.println:(I)V
   64:  return
}

7. Použití řetězců v příkazu switch

Nyní se opět vraťme k již zmíněné změně sémantiky příkazu switchJDK 7, tj. k zařazení možnosti použít výraz typu řetězec (přesněji řečeno výraz, který se vyhodnotí na řetězec) v rozhodovací části tohoto příkazu a řetězcové literály v jeho jednotlivých větvích. Na toto rozšíření mnoho vývojářů čekalo vlastně již od první verze Javy, protože řetězce jsou v tomto programovacím jazyku použity poněkud chaoticky – na jednu stranu se chovají jako další primitivní datový typ (existence řetězcového literálu a operátoru +, který je jinak vyhrazený pro primitivní datové typy), na stranu druhou jsou řetězce chápány jako běžné objekty, což třeba znamená, že operátor == nemusí vždy pracovat tak, jak si především začínající programátoři v Javě myslí, protože se porovnávají reference objektů nebo řetězců ze string poolu, nikoli samotný obsah řetězců. Ostatně si postačí vyzkoušet následující příklad, v němž se porovnává hodnota načtená z jeho prvního argumentu (zadaného na příkazové řádce) s řetězcovým literálem uloženým ve string poolu:

public class T
{
    public static void main(String[] args)
    {
        String s = (args.length > 0) ? args[0] : "";
        System.out.println(s);
        System.out.println(s == "ahoj");
        System.out.println("ahoj".equals(s));
        System.out.println("ahoj".compareTo(s));
    }

}

Poznámka: řetězec „ahoj“ z předchozího příkladu je ve string poolu (jenž je mj. součástí souboru .class) uložen pouze jedenkrát, proto pro něj – a jen pro něj – bude operátor == pracovat podobným způsobem, jako při porovnávání hodnot primitivních datových typů. Nemožnost použití řetězců v příkazu switch komplikovala některé úlohy, například zpracování parametrů, parsing některých typů souborů (příkladem mohou být soubory typu DXF) atd. Například při psaní funkce, která pro zadané jméno měsíce vypočítá počet dní v měsíci, se běžně používá sekvence zřetězených příkazů if-then (nejedná se o jedinou možnost, je samozřejmě možné řetězec převést na hodnotu výčtového typu pomocí metody valueOf, ovšem při pohledu do zdrojových kódů se zdá, že tuto možnost programátoři z nějakého důvodu příliš často nepoužívají):

public class StringIfEqualsTest
{
    public static final int YEAR = 2010;

    public static int getDaysOfMonth(String month)
    {
        if (month.equals("April") ||
            month.equals("June") ||
            month.equals("September") ||
            month.equals("November"))
        {
            return 30;
        }
        if (month.equals("January") ||
            month.equals("March") ||
            month.equals("May") ||
            month.equals("July") ||
            month.equals("August") ||
            month.equals("October") ||
            month.equals("December"))
        {
            return 31;
        }
        if (month.equals("February"))
        {
            return YEAR % 4 == 0 && (YEAR % 100 != 0 || YEAR % 400 == 0) ? 29 : 28;
        }
        else
        {
            throw new RuntimeException("Unknown month name: " + month);
        }
    }

    public static void main(String[] args)
    {
        String[] months = {"January", "February", "March",
                           "April",   "May",      "June",
                           "July",    "August",   "September",
                           "October", "November", "December"};

        for (String month : months)
        {
            int days = getDaysOfMonth(month);
            System.out.format("%-10s\t%d\n", month, days);
        }
    }
}

Předchozí programový kód, přesněji řečeno funkce getDaysOfMonth(), však s sebou přináší několik problémů. Především je vlastní logika této funkce schována za přemírou volání metod String.equals(), což zajisté nevede k lepší čitelnosti kódu. Co je však v některých případech téměř stejně závažné, je způsob, jakým je tato funkce provedena. Při pohledu do bajtkódu můžeme vidět, že se každý člen výrazu za if skutečně přeloží do volání metody String.equals(), což je poměrně neefektivní. Například pro měsíc Únor (February) je metoda String.equals() zavolána dvanáctkrát, což v tomto případě sice nebude mít žádný velký vliv na výkonnost, ovšem kdyby byly všechny řetězce stejně dlouhé, skutečně by se provádělo porovnání znak po znaku (pokud nemají řetězce shodnou délku, nemusí se porovnání samozřejmě provádět – viz též zdrojové kódy třídy String).

S využitím nové sémantiky příkazu switch je však možné stejný program napsat mnohem čitelněji:

public class StringSwitchTest
{
    public static final int YEAR = 2010;

    public static int getDaysOfMonth(String month)
    {
        switch (month)
        {
            case "April":
            case "June":
            case "September":
            case "November":
                return 30;
            case "January":
            case "March":
            case "May":
            case "July":
            case "August":
            case "October":
            case "December":
                return 31;
            case "February":
                return YEAR % 4 == 0 && (YEAR % 100 != 0 || YEAR % 400 == 0) ? 29 : 28;
            default:
                throw new RuntimeException("Unknown month name: " + month);
        }
    }

    public static void main(String[] args)
    {
        String[] months = {"January", "February", "March",
                           "April",   "May",      "June",
                           "July",    "August",   "September",
                           "October", "November", "December"};

        for (String month : months)
        {
            int days = getDaysOfMonth(month);
            System.out.format("%-10s\t%d\n", month, days);
        }
    }
}

8. Struktura generovaného bajtkódu při použití řetězců v příkazu switch

Vraťme se nyní k oběma demonstračním příkladům. Funkce getDaysOfMonth() z prvního příkladu (přeložitelného i na JDK 1.0) je přeložena do bajtkódu, z něhož je patrné, že se příkazy if s poměrně složitými podmínkami přeložily do sekvence instrukcí tvořících „špagetový kód“ tvořený mnoha podmíněnými skoky ifeq (branch if equal) a ifne (branch if not equal):

public static int getDaysOfMonth(java.lang.String);
  Code:
   0:   aload_0
   1:   ldc     #2;         //String April
   3:   invokevirtual   #3; //Method java/lang/String.equals:(Ljava/lang/Object;)Z
   6:   ifne    36          // větvení na základě výsledku metody String.equals()
   9:   aload_0
   10:  ldc     #4;         //String June
   12:  invokevirtual   #3; //Method java/lang/String.equals:(Ljava/lang/Object;)Z
   15:  ifne    36          // větvení na základě výsledku metody String.equals()
   18:  aload_0
   19:  ldc     #5;         //String September
   21:  invokevirtual   #3; //Method java/lang/String.equals:(Ljava/lang/Object;)Z
   24:  ifne    36          // větvení na základě výsledku metody String.equals()
   27:  aload_0
   28:  ldc     #6;         //String November
   30:  invokevirtual   #3; //Method java/lang/String.equals:(Ljava/lang/Object;)Z
   33:  ifeq    39
   36:  bipush  30          // cíl předchozích větvení - vrácení hodnoty 30
   38:  ireturn
   39:  aload_0
   40:  ldc     #7; //String January
   42:  invokevirtual   #3; //Method java/lang/String.equals:(Ljava/lang/Object;)Z
   45:  ifne    102
   48:  aload_0
   49:  ldc     #8; //String March
   51:  invokevirtual   #3; //Method java/lang/String.equals:(Ljava/lang/Object;)Z
   54:  ifne    102
   57:  aload_0
   58:  ldc     #9; //String May
   60:  invokevirtual   #3; //Method java/lang/String.equals:(Ljava/lang/Object;)Z
   63:  ifne    102
   66:  aload_0
   67:  ldc     #10; //String July
   69:  invokevirtual   #3; //Method java/lang/String.equals:(Ljava/lang/Object;)Z
   72:  ifne    102
   75:  aload_0
   76:  ldc     #11; //String August
   78:  invokevirtual   #3; //Method java/lang/String.equals:(Ljava/lang/Object;)Z
   81:  ifne    102
   84:  aload_0
   85:  ldc     #12; //String October
   87:  invokevirtual   #3; //Method java/lang/String.equals:(Ljava/lang/Object;)Z
   90:  ifne    102
   93:  aload_0
   94:  ldc     #13; //String December
   96:  invokevirtual   #3; //Method java/lang/String.equals:(Ljava/lang/Object;)Z
   99:  ifeq    105
   102: bipush  31
   104: ireturn
   105: aload_0
   106: ldc     #14; //String February
   108: invokevirtual   #3; //Method java/lang/String.equals:(Ljava/lang/Object;)Z
   111: ifeq    117
   114: bipush  28
   116: ireturn

Naproti tomu při čtení bajtkódu druhé funkce přeložené pomocí JDK 7, narazíme na poměrně velké odlišnosti. Především se nejdříve vypočte otisk (hash) řetězce month (otiskem je 32bitové celé číslo). Tento otisk je použit v instrukci lookupswitch při porovnávání s otisky řetězcových literálů. Zde je důležité především to, že tyto otisky jsou vypočteny již v době překladu, tj. v době běhu se jimi již JVM nemusí zdržovat. Pokud je otisk řetězce month shodný s otiskem nějakého literálu, je proveden skok do větve, v níž se zavolá metoda String.equals(), protože shoda otisků řetězců samozřejmě nemusí znamenat to, že jsou řetězce skutečně identické (jinými slovy – při hešování nutně dochází ke kolizím). Důležité je, že se metoda String.equals() zavolá maximálně jedenkrát (přesněji řečeno v našem případě maximálně jedenkrát, protože u námi použitých řetězcových literálů nedošlo ke kolizi), což je důležité, protože volání metody String.equals() je obecně mnohem pomalejší, než pouhé porovnání dvou 32bitových otisků.

V každé větvi je na zásobník uložena celočíselná konstanta, která je následně použita v instrukci tableswitch pro vrácení správné hodnoty z celé funkce. Výsledný bajtkód je sice poněkud delší, ovšem je na druhou stranu proveden mnohem rychleji, a to zejména v případech, kdy počet větví příkazu switch roste:

public static int getDaysOfMonth(java.lang.String);
  Code:
   0:   aload_0
   1:   astore_1
   2:   iconst_m1
   3:   istore_2
   4:   aload_1
   5:   invokevirtual   #2; //Method java/lang/String.hashCode:()I
   8:   lookupswitch{ //12
                -199248958: 275;  // otisky řetězců jednotlivých měsíců + cílová adresa skoku
                -162006966: 172;
                -25881423: 144;
                77125: 200;
                2320440: 215;
                2320482: 130;
                43165376: 245;
                63478374: 116;
                74113571: 186;
                626483269: 260;
                1703773522: 158;
                1972131363: 230;
                default: 287 }
   116: aload_1
   117: ldc     #3; //String April
   119: invokevirtual   #4; //Method java/lang/String.equals:(Ljava/lang/Object;)Z
   122: ifeq    287
   125: iconst_0
   126: istore_2
   127: goto    287
   130: aload_1
   131: ldc     #5; //String June
   133: invokevirtual   #4; //Method java/lang/String.equals:(Ljava/lang/Object;)Z
   136: ifeq    287
   139: iconst_1
   140: istore_2
   141: goto    287
   144: aload_1
   145: ldc     #6; //String September
   147: invokevirtual   #4; //Method java/lang/String.equals:(Ljava/lang/Object;)Z
   150: ifeq    287
   153: iconst_2
   154: istore_2
   155: goto    287
   158: aload_1
   159: ldc     #7; //String November
   161: invokevirtual   #4; //Method java/lang/String.equals:(Ljava/lang/Object;)Z
   164: ifeq    287
   167: iconst_3
   168: istore_2
   169: goto    287
   172: aload_1
   173: ldc     #8; //String January
   175: invokevirtual   #4; //Method java/lang/String.equals:(Ljava/lang/Object;)Z
   178: ifeq    287
   181: iconst_4
   182: istore_2
   183: goto    287
   186: aload_1
   187: ldc     #9; //String March
   189: invokevirtual   #4; //Method java/lang/String.equals:(Ljava/lang/Object;)Z
   192: ifeq    287
   195: iconst_5
   196: istore_2
   197: goto    287
   200: aload_1
   201: ldc     #10; //String May
   203: invokevirtual   #4; //Method java/lang/String.equals:(Ljava/lang/Object;)Z
   206: ifeq    287
   209: bipush  6
   211: istore_2
   212: goto    287
   215: aload_1
   216: ldc     #11; //String July
   218: invokevirtual   #4; //Method java/lang/String.equals:(Ljava/lang/Object;)Z
   221: ifeq    287
   224: bipush  7
   226: istore_2
   227: goto    287
   230: aload_1
   231: ldc     #12; //String August
   233: invokevirtual   #4; //Method java/lang/String.equals:(Ljava/lang/Object;)Z
   236: ifeq    287
   239: bipush  8
   241: istore_2
   242: goto    287
   245: aload_1
   246: ldc     #13; //String October
   248: invokevirtual   #4; //Method java/lang/String.equals:(Ljava/lang/Object;)Z
   251: ifeq    287
   254: bipush  9
   256: istore_2
   257: goto    287
   260: aload_1
   261: ldc     #14; //String December
   263: invokevirtual   #4; //Method java/lang/String.equals:(Ljava/lang/Object;)Z
   266: ifeq    287
   269: bipush  10
   271: istore_2
   272: goto    287
   275: aload_1
   276: ldc     #15; //String February
   278: invokevirtual   #4; //Method java/lang/String.equals:(Ljava/lang/Object;)Z
   281: ifeq    287
   284: bipush  11
   286: istore_2
   287: iload_2
   288: tableswitch{ //0 to 11
                0: 352;      // zde již máme všech 13 cílů skoku: 12 měsíců + větev default
                1: 352;
                2: 352;
                3: 352;
                4: 355;
                5: 355;
                6: 355;
                7: 355;
                8: 355;
                9: 355;
                10: 355;
                11: 358;
                default: 361 }
   352: bipush  30
   354: ireturn
   355: bipush  31
   357: ireturn
   358: bipush  28
   360: ireturn

9. Odkazy na Internetu

  1. OpenJDK Source Releases
    http://downlo­ad.java.net/o­penjdk/jdk7/
  2. Java Platform, Standard Edition 7 Source Snapshot Releases
    http://downlo­ad.java.net/jdk7/
  3. Control Flow in the Java Virtual Machine
    http://www.ar­tima.com/under­thehood/flowP­.html
  4. Java Virtual Machine
    http://en.wiki­pedia.org/wiki/Ja­va_virtual_machi­ne
  5. ==, .equals(), compareTo(), and compare()
    http://leepoin­t.net/notes-java/data/expres­sions/22compa­reobjects.html
  6. New JDK7 features
    http://openjdk­.java.net/pro­jects/jdk7/fe­atures/
  7. Joe Darcy blog about JDK
    http://blogs.sun­.com/darcy
  8. Java 7 – more dynamics
    http://www.baptiste-wicht.com/2010/04­/java-7-more-dynamics/
Našli jste v článku chybu?
Podnikatel.cz: Reverse – charge dopadá na další služby

Reverse – charge dopadá na další služby

Root.cz: Legendární hra Quake slaví 20. narozeniny

Legendární hra Quake slaví 20. narozeniny

120na80.cz: Bylinka pro dobrý sex. Jaká to je?

Bylinka pro dobrý sex. Jaká to je?

Vitalia.cz: Jak pít při sportu

Jak pít při sportu

Lupa.cz: Hackujete? Můžete mít problém sehnat práci

Hackujete? Můžete mít problém sehnat práci

Lupa.cz: Milý deníčku, teď mi tě bude psát aplikace

Milý deníčku, teď mi tě bude psát aplikace

Lupa.cz: Pirate Bay se drží 13 let. A bojuje s Hollywoodem

Pirate Bay se drží 13 let. A bojuje s Hollywoodem

Vitalia.cz: „Nepřeskakujte“ praktické lékaře

„Nepřeskakujte“ praktické lékaře

DigiZone.cz: Film+ a nevhodné snímky přes den

Film+ a nevhodné snímky přes den

Vitalia.cz: Budou i v Česku GM potraviny bez označení?

Budou i v Česku GM potraviny bez označení?

Podnikatel.cz: SMS oznamují nedoplatek na dani, nic neplaťte

SMS oznamují nedoplatek na dani, nic neplaťte

Lupa.cz: První robotické taxíky už vozí pasažéry

První robotické taxíky už vozí pasažéry

Vitalia.cz: I takové burgery nabídla Veggie náplavka

I takové burgery nabídla Veggie náplavka

Lupa.cz: Jak připojit Colours? Gong má chytrou Wi-Fi

Jak připojit Colours? Gong má chytrou Wi-Fi

Lupa.cz: Samořídicí taxíky jsou tu. Začíná s nimi Uber

Samořídicí taxíky jsou tu. Začíná s nimi Uber

Podnikatel.cz: OSA zdraží, ale taky přidá nový poplatek

OSA zdraží, ale taky přidá nový poplatek

Podnikatel.cz: Česká pošta vycouvala ze služby ČP Cloud

Česká pošta vycouvala ze služby ČP Cloud

Měšec.cz: MojeMobilní karta: vychytávky pro Android

MojeMobilní karta: vychytávky pro Android

Podnikatel.cz: Čauky mňauky. Proč hledají lidé tento výraz?

Čauky mňauky. Proč hledají lidé tento výraz?

Podnikatel.cz: Mění se pravidla pro rodičovskou

Mění se pravidla pro rodičovskou