Firma SUN Microsystems chystá uvedenie novej verzie jazyka Java 2 SE 1.5.0, ktoré síce nie je tak prelomové ako bol prechod z verzie 1 na verziu 2, napriek tomu však obsahuje veľké množstvo noviniek. V niektorých prípadoch ide o naozaj zásadné veci, týkajúce sa tradične bezpečnosti a interoperability jazyka Java. Väčšina noviniek a vylepšení je zameraná na uľahčenie práce programátora, ale nájdu sa aj také, ktoré potešia hlavne používateľov.

Posledná pripravovaná verzia platformy Java 2 Standard Edition 1.5.0, známa pod kódovým označením „Tiger“, je výsledkom viac ako 15tich JSR (Java Specification Request) komponentov a takmer 100 dôležitých vylepšení, vzniknutých prostredníctvom JCP (Java Community Process).

Logicky teda prináša veľké množstvo rôznych väčších či menších vylepšení. Úlohou tohto technologického preview je predstaviť vám najvýznamnejšie novinky, ktoré nová verzia prináša. Keďže ich je veľa, nie je to ľahká úloha. Pre vašu predstavu, najvýznamnejšie zmeny sa uskutočnili v oblasti zjednodušenia vývoja, škálovateľnosti a výkonu, monitorovania a manažmentu a tiež v oblasti desktopových klientov.

Generické typy (parametrizované typy)

Ide o veľmi dlho očakávané vylepšenie jazyka v oblasti práce s dátovými typmi. Generické typy zjednodušujú metódam prácu nad rôznymi typmi objektov v súčinnosti so zvýšenou bezpečnosťou v čase prekladu zdrojového kódu. Generické typy sú v Jave úplnou novinkou a je nutné sa s nimi naučiť pracovať. Radikálne zvyšujú najmä použiteľnosť kolekcií (Collections Framework) a eliminujú potrebu častého pretypovania medzi jednotlivými dátovými typmi.

Spomenul som, že generické typy radikálne zvyšujú použiteľnosť kolekcií, napr. LinkedList, ArrayList, alebo HashMap. Collections API obsahuje pomerne veľké množstvo implementovaných tried a rozhraní, ktoré môžu uchovávať rôzne objekty. Problémom však doteraz bola práve tá veľká variabilita dátových typov a s tým súvisiaca potreba častého pretypovania. To viedlo k chybám. Nasledujúci príklad využíva klasický princíp.

LinkedList list = new LinkedList();
list.add(0, new Integer(1));
int total = ((Integer)list.get(0)).intValue();

Potreba pretypovania na Integer v poslednom riadku je kameňom úrazu, ktorý generické typy môžu odstrániť. „Problém“ je v tom, že Collections API 1.4.2 používa na vnútorné ukladanie objektov triedu Objekt, čo neodstraňuje problém s nezlučiteľnosťou dátových typov. Predstavte si, že sa pokúsite vložiť napr. String do kolekcie označenej ako Integer. A je tu problém, ktorý sa však nemusí odhaliť v čase prekladu kódu. V lepšom prípade by mal byť odchytený v procese testovania, kedy vznikne výnimka ClassCastException. V horšom prípade (nie všetko sa detailne testuje), vznikne výnimka za behu programu. Nasleduje ukážka, ktorá s využitím generického typu daný problém rieši.

LinkedList<Integer> list = new LinkedList<Integer>();
list.add(0, new Integer(1));
int total = list.get(0).intValue();

Týmto zápisom sme deklarovali, že kolekcia LinkedList je typu Integer, a akýkoľvek pokus o vloženie iného dátového typu bude odhalený počas kompilácie. Všimnite si tiež, že nie je nutné vykonať prevod z Integer na int, kompilátor sa o to postará sám. Spojenie generických typov a kolekcií (vrátane rôznych typov zoznamov; trieda List) mení spôsob práce s dátami. Nepracuje sa nad špecifickým dátovým typom, ale nad homogénnou množinou dát, ktorej typ je definovaný pri jej deklarácii.

Najlepším spôsobom ako pochopiť generické typy, je študovať Java kód, ktorý ťaží z ich výhod. Preto uvediem komplexnejší príklad, napísaný v aktuálnej špecifikácii a obsahujúci dva zoznamy objektov String a Integer. Keďže obidva objektové typy sú zdedené z triedy Object, je možné aplikovať metódy triedy List na obidva zoznamy. Problém je v tom, že z pohľadu kompilátora sú jednotlivé elementy zoznamov typu Object, a nič nám nebráni navzájom miešať typy. Ešte horšie je, že vývojár sa môže zmýliť, navzájom si zoznamy zameniť a vykonať nelegálne pretypovanie, čo sa odhalí až za behu programu. Preštudujte si nasledujúci príklad.

List integerList = new LinkedList();
List stringList = new LinkedList();
integerList.add(new Integer(1));
integerList.add(new Integer(2));
stringList.add(new String(„Hello!“));
// Nič nám nebráni vykonať nasledovné.
stringList.add(new Integer(1));
Iterator listIterator = integerList.iterator();
/* Kompilátor sa nestará o návratový typ elementu, pričom
programátor má v úmysle prechádzať cez zoznam obsahujúci reťazce.*/

while(listIterator.hasNext()) {
  // Ilegálne pretypovanie, ktoré …
  String item = (String)listIterator.next();
}
listIterator = stringList.iterator();
while (listIterator.hasNext()) {
  // … neprejde pri behu programu
  String item = (String)listIterator.next();
}

Je jasné, že vyššie uvedený príklad môže v reálnej aplikácii narobiť problémy. S použitím generických typov však zavedieme silnú kontrolu, kedy kompilátor dokáže rozoznať rozdiel medzi uvedenými dvoma zoznamami a zabezpečí požadovanú homogenitu množiny elementov. Preto príklad upravíme tak, aby kompilátor rozoznal možné problémy.

LinkedList<Integer> integerList = new LinkedList<Integer>();
LinkedList<String> stringList = new LinkedList<String>();
integerList.add(new Integer(1));
integerList.add(new Integer(2));
stringList.add(new String(„Hello!“));
// výnimka pri kompilácii
stringList.add(new Integer(1));
Iterator<Integer> listIterator = integerList.iterator();
String item;
while(listIterator.hasNext()) {
  item = listIterator.next();
}
// výnimka pri kompilácii
listIterator = stringList.iterator();
while (listIterator.hasNext()) {
  item = listIterator.next();
}

Doteraz sme použili generické typy len pre kontajnerové triedy, ale rovnako si môžete zadeklarovať svoje vlastné triedy, rozhrania a metódy. Nasledujúca tabuľka obsahuje syntax, ktorú musíte dodržať, pričom často máte na výber z viacerých možností.

variant syntax
typ Map<String> strMap = new Map<String>()
List<I extends N> intList = new List<I extends N>()
Map<? extends I> map = new Map<? extends I>()
List<?> intList = new List<?>()
rozhranie interface List<String> {...}
interface List<String> implements MyInterface{...}
interface List<I extends N> {...}
interface List<? extends I> implements MyInterface{...}
interface List<?> {...}
trieda class MyList<Integer> {...}
class MyList<String> implements List<String> {...}
class MyList<I extends N> implements List<Integer> {...}
metóda static<String> String getAll(List<String> list);

Autoboxing a auto-unboxing primitívnych dátových typov

Ako iste viete primitívne dátové typy ako je int, double, boolean a pod. majú svoje objektové náprotivky Integer, Double a Boolean. Java má teda v tomto smere zdvojený systém. Tento systém však často prináša nutnosť konverzie medzi primitívnym typom a jeho náprotivkom. Hlavne v prípade využívania kolekcií a kontajnerových tried všeobecne. V pripravovanej verzii jazyka 1.5 sa preto zaviedol systém (boxing a unboxing), ktorý by mal odstrániť túto nutnosť častých konverzií. Tento systém tak nielenže znižuje prácnosť pri tvorbe kódu, ale zároveň aj robí kód prehľadnejším, ľahšie udržiavateľným a znižuje pravdepodobnosť výskytu chýb.

Vo verzii 1.4.2 by musel váš kód vyzerať nasledovne:

ArrayList list = new ArrayList();
list.add(0, new Integer(123)); //manual boxing
int total = ((Integer)list.get(0)).intValue();//manual unboxing

Vo verzii 1.5.0 už môžete s výhodou použiť nasledovnú konštrukciu:

ArrayList<Integer> list = new ArrayList<Integer>();
list.add(0, 123); //autoboxing
int total = list.get(0); //auto-unboxing

Ďalšie informácie ohľadom autoboxingu a auto-unboxingu nájdete na tejto stránke.

Vylepšený príkaz „for“

V súvislosti s využívaním frameworku Collections sa intenzívne využíva trieda Iterator. Táto trieda poskytuje mechanizmus na sekvenčné prechádzanie a prehľadávanie objektov kontajnerových tried (Collections API). Väčšinu času sa Iterator využíva na získavanie jednotlivých alebo konkrétnych elementov kontajnerových tried (napr. ArrayList). Nový vylepšený príkaz for, vás oslobodí od tejto rutiny a prenechá nasadenie iterátora na kompilátor. Nasledovná ukážka znázorňuje postup, ktorý je v súčasnosti nutné použiť.

ArrayList list = new ArrayList();
list.add(0, new Integer(123));
for (Iterator i = list.iterator(); i.hasNext();) {
 Integer value = ((Integer) i.next());
}

Čiže je nutné najprv ručne získať objekt Iterator danej kolekcie a následne volať metódu hasNext(). Keďže metóda next() vracia objekt triedy Object, je väčšinou nutné urobiť pretypovanie. Ak použijete vylepšený for spolu s generickými typmi a autoboxingom, vyššie uvedený príklad bude vyzerať nasledovne:

ArrayList<Integer> list = new ArrayList<Integer>();
list.add(0, 123);
for (Integer i : list) {
 Integer value = list.get(i);
}

Ďalšie informácie týkajúce sa vylepšeného for nájdete na tejto stránke.

Typovo bezpečná deklarácia „enum“

V novej verzii sa zavádza nový typ deklarácie enum. Ide o typovo bezpečné, výkonné pole objektov. Vo svojej najjednoduchšej verzii sú rovnaké ako C/C++ enumerácie.

public enum Semafor { červená, oranžová, zelená }

Ich deklarácia však môže byť ďaleko komplikovanejšia, okrem konštánt môže obsahovať deklaráciu konštruktora, privátne premenné a metódy na prístup k nim. Tu je príklad:

public enum Bankovka {
 dvadsať(20), päťdesiat(50), sto(100), dvesto(200);
 Bankovka(int value) { this.value = value; }
 private final int value;
 public int value() { return value; }
}

Tento príklad ukazuje ako je možné definovať enumeráciu Bankovka. V prvom riadku sú jednotlivé elementy. Všimnite si ako využívajú konštruktor tejto enumerácie, pričom jednotlivé čísla predstavujú ich hodnotu v korunách. Nasleduje definícia samotného konštruktora, potom privátna premenná a nakoniec metóda na prístup k tejto premennej. Viac detailnejších informácií k novému typu deklarácie nájdete na tejto stránke.

Statický import

Statický import vám umožňuje používať statické členy, resp. konštanty, bez nutnosti použiť názov triedy ako prefix. Uvediem príklad, pričom najprv si vytvoríme triedu definujúcu tri konštanty.

package math;
public class MathConstants {
 public static final double PI = 3.141592653589793;
 public static final double EULER = 0.5772156649015328;
}

Nasledujúci príklad ukazuje použitie statického importu:

import static math.MathConstants;
class Math {
 private static double PI = PI;
 private static double EULER = EULER;
}
// Vo verzii 1.4.2 by ste museli použiť nasledovný zápis:
import math.MathConstants;
class Math {
 private static double PI = MathConstants.PI;
 private static double EULER = MathConstants.EULER;
}

Ďalšie podrobné informácie nájdete na tejto stránke.

V súvislosti s novou verziou jazyka Java 1.5.0, je pripravovaných ďaleko viac noviniek. Pokúsil som sa však vybrať niekoľko snáď najzaujímavejších. Avšak pre kompletnosť, na koniec článku vkladám odkazy na stránky týkajúce sa ďalších, nie však všetkých, noviniek.

Odkazy, zdroje

Starší komentáře ke článku

Pokud máte zájem o starší komentáře k tomuto článku, naleznete je zde.

Žádný příspěvek v diskuzi

Odpovědět