Java 1.5: Tygr vystrkuje drápy

Poslední dobou stále sílí zájem o informace týkající se připravované nové verze jazyka Java, která nese kódové o...


Poslední dobou stále sílí zájem o informace týkající se připravované nové verze
jazyka Java, která nese kódové označení Tiger. Nová verze se nebude omezovat
pouze na rozšíření standardní knihovny, ale má přinést několik zásadních
inovací i do samotného jazyka. Jedná se přitom o největší zásah do jazyka od
jeho vzniku. Novinek v Javě 1.5 je samozřejmě mnoho, ale největší zájem
vyvolávají právě návrhy na vylepšení vlastního jazyka, především jim se proto
budeme věnovat i v tomto článku.
Od svého vzniku (přesněji od svého představení veřejnosti) v roce 1995 byl
prozatím jazyk Java rozšířen pouze dvakrát: v roce 1997 přišla Java 1.1 s
koncepcí vnořených, vnitřních a lokálních tříd a v roce 2001 přidala Java 1.4
klíčové slovo assert. Java 1.5 však přichází se změnami daleko zásadnějšími
dohromady lze očekávat sedm rozšíření vlastního jazyka.
Všechna rozšíření jsou vedena několika společnými myšlenkami:
Zvýšit přehlednost zdrojového kódu.
Zvýšit spolehlivost a bezpečnost přeložených programů.
Minimalizovat nekompatibilitu s předchozími verzemi.
Obejít se beze změn ve funkci virtuálního stroje.
Dosavadní programy musejí bez problémů pracovat.
Pokud možno nezavádět nová klíčová slova (bylo zavedeno pouze jediné nové
klíčové slovo enum).

Generické datové typy
Asi nejdůležitějším rozšířením je zavedení generických datových typů, které by
si samy zasloužily vlastní článek. Pokusme se alespoň o stručné shrnutí.
Jednou z klíčových otázek řešenou tvůrci jazyků a standardní knihovny je způsob
definice kontejnerů. Kontejner je objekt, který slouží k uchovávání jiných
objektů či odkazů na ně. Nejznámějším (statickým) kontejnerem je pole. Soudobé
programy ale stále více používají i dynamické kontejnery jako seznam, frontu,
množinu, slovník apod.
Snahou tvůrců jazyků a jejich knihoven je definovat kontejnery tak, aby pro
všechny datové typy sloužila jedna společná definice kontejneru. Současné
překládané jazyky to většinou řeší tak, že dynamické kontejnery nejsou součástí
definice jazyka a v knihovně jsou naprogramovány pro společného rodiče všech
ukládaných dat (v Javě jde o třídu Object, .Net i Delphi to řeší obdobně).
Jedním z nedostatků tohoto řešení je, že kontejnerové třídy pak nejsou schopny
při překladu zabezpečit typovou kontrolu ukládaných a vybíraných dat. Při
vybírání dat z kontejneru se navíc musejí získané hodnoty většinou ještě
přetypovat.
Java patří k jazykům, které jsou schopny provádět typovou kontrolu alespoň za
běhu programu při přetypovávání vybíraných dat. To je sice lepší, než když se
neprovádí vůbec, ale na druhou stranu takováto kontrola za prvé zdržuje a za
druhé poskytuje při chybě jen malé (pokud vůbec nějaké) vodítko k odhalení
zdroje chyby. Nejlepší by samozřejmě bylo, kdybychom v deklaraci kontejneru
mohli nějakým způsobem zadat typ uchovávaných dat.
Při ukládání by pak za nás překladač hlídal typ ukládaných dat a při vybírání
bychom vybíraná data nemuseli explicitně přetypovávat. Jedno možné řešení
ukazují šablony jazyka C++. Toto řešení je však (jako většina věcí v C++) velmi
rafinované a skýtá bezpočet možností, takže je plně chápou pouze někteří
programátoři.
Tvůrci Javy zvolili namísto šablon generické typy. Jejich řešení se snaží být
co nejjednodušší a přitom pokrývat hlavní problémy včetně některých, které
šablony C++ neřeší. Ukažme si vše přímo ve zdrojovém kódu. Klasický seznam
bychom mohli v současné verzi jazyka Java deklarovat a použít např. následovně:
interface List {
void add (Object x);
Iterator iterator();
}
interface Iterator {
Object next();
boolean hasNext();
}
//Použití v kódu
//...
List xs = new LinkedList();
xs.add (new Integer(0));
Integer x = (Integer)xs.itera
tor.next();
//...
Při využití generických datových typů by ekvivalentní kód vypadal takto:
interface List<T> {
void add (T x);
Iterator<T> iterator();
}
interface Iterator<T> {
T next();
boolean hasNext();
}
//Použití v kódu
//...
List<Integer> xs = new LinkedList
<Integer>();
xs.add (new Integer(0));
Integer x = xs.iterator.next();

Možnosti jsou ale mnohem bohatší. Generické typy umožňují např. vnořování a
předepsání závislosti jednoho typu na jiném.

Rozšíření příkazu for
Při práci s kontejnery je velice často potřeba provést nějakou operaci se všemi
prvky uloženými v kontejneru. Např. metodu, která by vracela průměr hodnot
uložených v kontejneru, bychom naprogramovali následovně:
double prumer (Collection sada) {
double soucet = 0;
for (Iterator iter = sada.itera
tor();
iter.hasNext; ) {
Double d = (Double)iter.next();
soucet += d.doubleValue();
}
return (soucet / sada.size());
}
Některé programovací jazyky zavádějí pro tento účel speciální příkaz cyklu,
označovaný většinou jako příkaz for each. Absenci tohoto příkazu tvůrcům Javy
vývojáři vyčítali. Ve verzi 1.5 již bude tato drobná kosmetická vada
odstraněna. Protože si však tvůrci jazyka předsevzali, že se pokusí
nerozšiřovat počet klíčových slov, nezaváděli nové klíčové slovo each, ale
definovali konstrukci, která pro označení zdroje dat využívá dvojtečku.
Ekvivalent předchozího programu by proto nyní (s využitím generických typů)
vypadal následovně:
double prumer (Collection<Double> sada) {
double soucet = 0;
for (Double d : sada)
soucet += d.doubleValue();
return (soucet / sada.size());
}
Nová verze příkazu for je použitelná nejenom pro dynamické kontejnery, ale i
pro statické kontejnery, pole. V takovém případě by mohla být předchozí metoda
definována následovně:
double prumer (double[] pole) {
double soucet = 0;
for (double d : pole) soucet += d;
return (soucet / pole.length);
}

Automatické převody
Java rozděluje datové typy na objektové a primitivní. V řadě případů je nutno
použít objektové datové typy a pro tento účel má každý primitivní datový typ
definován svůj "obalový" typ, jehož instance zastoupí hodnotu příslušného
primitivního typu. Doposud však programátoři museli instance příslušných
obalových typů vytvářet explicitně sami a sami také museli vyvolávat jejich
metody, které vracejí obalenou hodnotu.
Zavedením automatických převodů tato nutnost odpadá. Překladač nyní v
potřebných případech vytvoření instance obalových typů sám doplní. Současně je
možno tyto instance používat ve výrazech stejně, jako bychom používali jejich
odpovídající primitivní typy. Metoda vracející průměr hodnot prvků v kontejneru
by se tak mohla ještě zjednodušit a mohla by vypadat např. následovně:
double prumer ( List<Double> seznam )
{
double soucet = 0;
for (Double d : seznam) soucet += d;
return soucet / seznam.size();
}

Výčtové typy
Autor Javy nezahrnul do první definice jazyka výčtové typy, protože se mu
nepovedlo vymyslet, jak je definovat, aniž by jejich používání porušovalo
zásady objektově orientovaného programování (OOP), jak je tomu např. v C++ nebo
v C#. Joshua Bloch však nedávno ve své knize Effective Java navrhl způsob, jak
takovéto typy definovat, aniž bychom zásady OOP porušovali. Ukázal, jak
definovat výčtový typ jako standardní třídu se soukromým konstruktorem, přičemž
jednotlivé hodnoty jsou jejími statickými atributy. Tím obešel nedostatky
definice těchto typů z C++ i jazyků platformy .Net.
V Javě 1.5 je jeho myšlenka dotažena do konce zavedením konstrukce, která
definici hodnot výčtového typu zjednodušuje. Použití hodnot takto definovaného
výčtového typu umožňuje typovou kontrolu a vzhledem k tomu, že tento typ je
klasická třída, můžeme v něm definovat i vlastní atributy a metody. Typ Smer
definující hlavní světové strany můžeme v Javě 1.5 definovat např. následovně
(ve skutečném programu bychom definici nejspíše ještě doplnili o metody pro
podporu serializovatelnosti, pro práci s kolekcí všech směrů, pro otočky
vlevoVbok, vpravoVbok, celemVzad a o další užitečné metody):
public enum Smer {
VYCHOD ("Východ"), SEVER ("Sever"),
ZAPAD ("Západ"), JIH ("Jih");
String nazev;
private Smer (String nazev)
{

this.nazev = nazev;
}
public String toString() {
return nazev;
}
}

Počet parametrů
Java doposud nezavedla metody s proměnným počtem parametrů tuto absenci bylo
nutno obcházet pomocí kontejnerů. Např. funkci max jsme museli deklarovat např.
public static int max (int[] args) {...};
a používat např.
int m = max (new int[] { 0, a, b, c, d });
Nová Java se chystá zavést možnost definovat poslední vektor v seznamu
parametrů klauzulí
Typ... jmenoParametru
s tím, že jeho jednotlivé prvky bude možno zapisovat do seznamu parametrů
přímo. Pak bychom mohli deklarovat obdobu klasické céčkové funkce printf:
void printf(String format, Object... args);
a při využití automatického vytváření obalových typů použít tuto metodu např.
následovně:
Smer s = VYCHOD;
int nr = otocenychNa( s );
printf ( "%i robotů je otočeno na %s", ni, s );

Import statických členů
Při používání statických atributů a metod jiných tříd je třeba jejich volání
kvalifikovat názvem příslušné třídy. Řada programátorů obchází tuto nutnost
tím, že definuje konstanty v rámci rozhraní, k jehož implementaci se pak třída,
v níž mají být konstanty použity, přihlásí. Takovýto postup však opět porušuje
zásady OOP.
Abychom mohli mít své programy méně "ukecané", aniž bychom museli porušovat
zásady OOP, zavádí Java 1.5 možnost importu statických členů (atributů i
metod), který umožní vynechávat explicitní kvalifikaci. Např. místo doposud
používaného zápisu
x = Math.cos( Math.PI * alfa);
bychom při použití nově povoleného importu statických členů:


import static Math.*;
mohli použít zápis
x = cos(PI * alfa);

Metadata
Stále častěji se objevují nástroje a technologie, které ke své práci vyžadují
informace, jež není možno jednoduše zanést do vlastního kódu. Např. mnohá API,
zejména pak API pro distribuované systémy a webové služby, požadují po
programátorech, aby vedle vlastního kódu vytvářené komponenty naprogramovali
ještě množství různé "vaty", potřebné pro správnou funkci výsledného programu
(většinou jde o nějaká podpůrná rozhraní). V jejím případě se předem přesně ví,
jak má vypadat, takže její příprava je v podstatě mechanickou záležitostí. Java
1.5 přichází s myšlenkou odbourání této mechanické práce zavedením tzv.
metadat. V současné době bychom např. definovali komponentu pro správu
objednávek následovně:
public class ObjednavkyImpl imple
ments Objednavky {
public Zbozi[] getNabidka() {
//...
}
public String objednej (String nazev, int pocet) {
//...
}
}
J2EE vyžaduje pro správnou činnost této komponenty definici rozhraní ("vaty"):
public interface Objednavky extends java.rmi.Remote {
public Zbozi[] getNabidka()
throws java.rmi.RemoteExcepti
on; public String objednej (String nazev, int pocet)
throws java.rmi.RemoteException;
}
Klíčovou povinností vývojáře je průběžně sledovat, aby se při všech
modifikacích promítly změny kódu do oné "vaty" a naopak. Při využití metadat
bychom mohli kód komponenty definovat např. následovně:
public class ObjednavkyImpl implements Objednavky {
@Remote public Zbozi[] getNabidka() {
//...
}
@Remote public String objednej
(String nazev, int pocet) {
//...
}
}
Metadatový příkaz @Remote by pak zabezpečil, aby použité vývojové prostředí
vygenerovalo potřebné rozhraní při překladu a sestavování aplikace samo.

Závěr
Rozšíření syntaxe jazyka umožňují dále zvýšit jak efektivitu vývoje programů,
tak kvalitu výsledného kódu. Chcete-li si je vyzkoušet, můžete si nainstalovat
předběžnou verzi překladače, ve které jsou již zakomponovány veškeré změny s
výjimkou metadat (viz odkazy).
Podrobný výklad nebezpečnosti některých způsobů definice konstant a výčtových
typů spolu s návrhem vzoru implementovaného v nové verzi Javy (a řadou dalších
zásad, jak vytvářet efektivní, robustní a snadno udržovatelné programy) najdete
v knize J. Bloch: Effective Java, Addison Wesley 2001 (český překlad Java
efektivně, Grada 2002).

Doporučené odkazy
http://developer.java.sun.com/developer/earlyAccess/adding_generics předběžná
verze překladače, ve které jsou již zakomponovány veškeré změny s výjimkou
metadat
http://jcp.org/aboutJava/communityprocess/jsr/tiger/ návrh specifikace
některých navrhovaných rozšíření jazyka
http://servlet.java.sun.com/javaone/ prezentace některých přednášek z červnové
konference JavaOne (ve formátu PDF). K jejich stažení však musíte být členy
Java Inner Circle









Komentáře
K tomuto článku není připojena žádná diskuze, nebo byla zakázána.