Hlavní navigace

Novinky v Javě aneb Tygří spáry

Petr Sickboy Hejl 7. 11. 2003

Zřejmě již koncem tohoto roku uvolní společnost Sun Microsystems, Inc. beta verzi Java Platform Standard Edition 1.5 (codename Tiger). Její autoři tvrdí, že jde o největší zásah do jazyka Java od jeho vzniku. Slibují kratší a čitelnější kód, samozřejmě při zachování kompatibility s předchozími verzemi. Na co se můžete těšit, vám odpoví následující článek.

Opravdu nová Java

Až dosud byly do nových verzí, až na výjimky (např. vnitřní a vnořené třídy, později assertions), přidávány balíky, které rozšiřovaly funkčnost dosud nepokrytým směrem, jako například java.nio a java.util.logging ve verzi 1.4, spolu s vylepšováním JVM a překladače. V nové verzi 1.5 však přibudou přímo nové konstrukty (novým klíčovým slovům se autoři až na jedno vyhnuli), jejichž cílem je zejména zpřehlednit kód a přenést psaní rutinních částí kódu z programátora na překladač. Programátor se tak vyvaruje některých zbytečných chyb a některé typové kontroly bude možné oproti dřívější verzi provést již při překladu.

Mezi hlavní novinky nové verze mají patřit:

  • Generické datové typy
  • Rozšířený cyklus for
  • Autoboxing/Unboxing
  • Výčtový typ
  • Static import
  • Proměnný počet parametrů
  • Metadata

Navíc má být v nové verzi přepracován a vylepšen paměťový model.

Následují části, které stručně přiblíží tato nová vylepšení jazyka. Rozhodně se nejedná o vyčerpávající popis – k tomu by bylo třeba desítek stran a není to ani posláním tohoto článku. Pro vysvětlení jsou použity jednoduché části kódu, které (snad) usnadní pochopení nových principů.

Generické datové typy

Tato poměrně značná inovace má zajistit zejména compile-time typovou kontrolu pro prvky kontejneru. Návrh je opravdu rozsáhlý, a proto se zaměříme jen na hlavní myšlenky. Základem je možnost definovat parametrizovaný typ (do určité míry jde o obdobu šablon v C++). Následuje jednoduchý příklad definice rozhraní a třídy, které používají generické typy:

interface Storage<A>{
  A get();
  void put(A element);
}
...
class Test<A> implements Storage<A>{
  private A test;
  public A get(){
    return test;
  }
  public void put(A element){
    test = element;
  }
}

Použití by pak mohlo být následující:

Storage<Integer> s = new Test<Integer>();
s.put(new Integer(7));
...
System.out.println(s.get().intValue());

Je zřejmé, že generické typy naleznou uplatnění zejména pro kontejnery (v souvislosti s tím také bude upraveno API některých tříd v balíku java.util). Pokud není parametrický typ při vytváření instance uveden, použije se java.lang.Object, což zajistí kompatibilitu již existujícího kódu. Jako parametr nebude možné uvést primitivní datový typ, a to z důvodu zachování odděleného používání primitivních a referenčních datových typů v Javě.

Při použití pro kontejnery je tedy hlavní výhodou kontrola typu vkládaných prvků. U prvků získaných z kontejneru naopak odpadá přetypování z typu Object na skutečně uložený typ. Dostatečně ilustrativní by měl být následující příklad, kdy procházíme kolekci obsahující prvky typu String pomocí iterátoru a odstraňujeme objekty specifických vlastností. Nejprve klasická v současnosti používaná konstrukce:

static void removeHello(Collection c){
  for (Iterator i = c.iterator(); i.hasNext();){
    String s = (String) i.next();
    if(s.equals("Hello")) i.remove();
  }
}

Následuje úprava s využitím generických datových typů:

static void removeHello(Collection<String> c){
  for (Iterator<String> i = c.iterator();
       i.hasNext();)
    if (i.next().equals("Hello")) i.remove();
}

Možností je samozřejmě mnohem více. Lze definovat metody, které mají jako návratový typ uveden generický typ, použít jej lze i ve vnitřních třídách apod.

Rozšířený cyklus for

Jde vlastně o nový příkaz, který se dá popsat jako foreach – in. Základní syntaxe je:

for( Type Identifier : Expression )
  Statement

Expression přitom musí být pole nebo musí implementovat nové rozhraní java.lang.Ite­rable. Zároveň se předpokládá změna rozhraní java.util.Collection tak, aby rozšiřovalo nebo implementovalo java.lang.Ite­rable. Základním přínosem tedy je, že při filtrování kontejneru odpadá nutnost volat metody iterátoru. Nejsnadnější bude opět uvést srovnávací příklad. V současnosti provedeme akci pro všechny prvky například takto:

void printHosts(Collection c){
  for (Iterator i = c.iterator(); i.hasNext();){
    URL url = (URL) i.next();
    System.out.println(url.getHost());
  }
}

To lze pomocí rozšířeného cyklu for zapsat například takto:

void printHosts(Collection c){
  for (Object o : c)
    System.out.println(((URL) o).getHost());
}

Při současném použití generických datových typů je pak možné celý zápis upravit dokonce na:

void printHosts(Collection<URL> c){
  for (URL url : c)
    System.out.println(url.getHost());
}

Je vidět, že výsledný kód je přehlednější a zároveň není na programátárovi požadováno, aby psal často se opakující a triviální část kódu.

Autoboxing/unboxing

Ve stejném duchu jako předchozí úpravy je i úprava tato. Protože jazyk Java má jak primitivní typy, tak reference na objekty, jsou pro primitivní typy definovány obalové objektové typy. Dosud však byl převod mezi nimi poměrně komplikovaný.

Při použití autoboxingu/un­boxingu se zápis výrazně zkrátí a zpřehlední. Obalovacímu typu lze přiradit přímo hodnotu typu, který je obalen. Přiřazení je možné provést i naopak. Předpoklad pro finální verzi je, že není možné převést hodnotu null na primitivní typ.

Je zřejmé, že použití této funkce se opět uplatní zejména při práci s kontejnery. Na něco takového jsem čekal již dlouho ;). Uveďme srovnávací příklad. Nyní:

int sumNumbers(List c){
  int sum;
  for (Iterator i = c.iterator(); i.hasNext();){
    Integer number = (Integer) i.next();
    sum += number.intValue();
  }
  return sum;
}

Bystrý čtenář navíc zjistil, že předchozí kód samozřejmě může generovat ClassCastException. Při použití dosud uvedených konstrukcí a autoboxingu/un­boxingu pak kód bude:

void sumNumbers(List<Integer> c){
  int sum;
  for (Integer number : c)
    sum += number;
  return sum;
}

Je vidět, že kód je kratší, přehlednější a kontrola typu proběhne již při překladu.

Výčtový typ

Další poměrně rozsáhlou změnou je zavedení výčtového typu. Jeho specifikace zabírá několik stránek, proto se ve stručnosti pokusím uvést hlavní myšlenky.

Výčtový typ je specifikován tak, že neporušuje zásady objektově orientovaného programování. Jde vlastně o třídu, která exportuje „self-typed“ (samotypované ;)) konstanty a nemá veřejný konstruktor. Výhodou je, že takto definované konstanty lze požít v přepínači switch i v kontejnerech, například jako klíč pro HashMap. Protože jde vlastně o třídu, je ve výčtovém typu možné definovat vlastní atributy a metody (zřejmě využijí jen zkušení programátoři). Výčtové typy budou mít společného předka java.lang.Enum a implementují rozhraní SerializableComparable.

Nejjednodušší (a zřejmě také nejčastější) definice výčtového typu bude například:

public enum Color{ RED, GREEN, BLUE }

Lze ale například také specifikovat jak se budou instance výčtového typu vytvářet:

public enum Compression{
  LOW(0.9), GOOD(0.5), EXCELLENT(0.3);
  private final double compr;
  Compression(double compr){
    this.compr = compr;
  }
  public double value() {
    return compr;
  }
}

Každý výčtový typ má také atribut VALUES, ve kterém je neměnitelný List položek výčtu v pořadí, v jakém byly definovány. Toho využívá následující příklad, který zároveň ukazuje použití přepínače:

for (Compression c : Compression.VALUES){
  switch(c){
    case Compression.LOW:
      System.out.println("Low: " + c.value());
      break;
    case Compression.GOOD:
      System.out.println("Good: " + c.value());
      break;
    case Compression.EXCELLENT:
      System.out.println("Excellent: " + c.value());
      break;
  }
}

Static import

Cílem tohoto zjednodušení má být opět zpřehlednění kódu. Jde vlastně o importování statických členů tříd, díky čemuž již není nutné uvádět u takových členů jméno třídy, ve které jsou obsaženy. Namísto

Math.abs(x);

můžeme tedy použít

import static java.lang.Math.*;
...
abs(x);

Použijeme-li výčtový typ pro definování vlastních konstant, lze je pak použít pomocí statického importu následujícím způsobem:

public enum TVColors{RED, GREEN, BLUE};
...
import static TVColors.*;
...
int color = RED;

Proměnný počet parametrů

Naprostou novinkou je v Javě také možnost definovat metodu s proměnným počtem parametrů. Základní syntaxi i práci s jednotlivými položkami nejlépe vysvětlí následující příklad, který ukazuje definici funkce  printf:

public static void printf(String format, Object... args){
  int i = 0;
  for (char c : format.toCharArray()){
    if (c == '%')
      System.out.print(args[i++]);
    else if (c == '\n')
      System.out.println();
    else
      System.out.print(c);
  }
}


Jako typ proměnného počtu parametrů (v příkladu Object) je možno použít jakýkoliv typ, včetně primitivního. Klauzuli Typ... argument lze pro danou metodu použít samozřejmě jen jednou a na konci seznamu parametrů. Skutečné parametry se pak při volání oddělují (klasicky) čárkou.

Metadata

Metadata je koncept, který by měl opět ulehčit život programátorům. V moderních API je totiž programátor nucen, aby splnil jejich požadavky, napsat určitou část kódu „zbytečně“. Uveďme příklad:

import java.rmi.*;
public interface DoAction extends Remote{
  public Object action() throws RemoteException;
}
public class DoActionImpl
 extends UnicastRemoteObject implements DoAction{
  public Object action() throws RemoteException{
    //implementace
  }
}

Definice rozhraní je v podstatě triviální. Navíc, změní-li se parametry volané metody, bude třeba upravit jak rozhraní, tak implementující třídu. Právě od těchto těžkostí by měla pomoci metadata.

Pomocí metadatového příkazu @Remote by se uvedená část kódu zapsala následujícím způsobem:

import java.rmi.*;
public class DoAction{
  @Remote public Object action(){
    //implementace
  }
}

Použitý vývojový nástroj by pak sám zajistil generování potřebného rozhraní a překlad. Koncept metadat navíc poskytuje kostru pro dodavatele vývojových nástrojů pro definování atributů. Například jsou již definovány metadatové příkazy pro Java Web Services. Sun předpokládá v této oblasti rozsáhlou činnost.

Závěr

Každý, kdo někdy v Javě napsal více než 100 řádků, jistě uvítá (tedy alespoň podle mého názoru) navržené změny. S úsporou napsaného kódu ubyde programátorských chyb a zároveň se zlepší čitelnost programů. Část odpovědnosti se tak přesune na kompilátor, který by měl nové konstrukce efektivně přeložit. Zároveň však zůstává zachována základní vlastnost Javy, tedy kompatibilita.

Všechny uvedené příklady kromě metadat jsem vyzkoušel v „EarlyAccess“ verzi kompilátoru. Stáhnout si jej můžete (po registraci) ze stránek java.sun.com.

Literatura

Návrh nových rozšíření jazyka
jcp.org/aboutJa­va/communitypro­cess/jsr/tiger/

Rozhovor s J. Blochem, softwarovým architektem Sun Microsystems, Inc.
java.sun.com/fe­atures/2003/05/­bloch_qa.html

Adding Generics to the Java Programming Language
www.lcc.uma.es/do­cencia/ETSIIn­f/isd/AddingGe­nericsToJava(sli­des).pdf


Java je registrovaná obchodní známka Sun Microsystems, Inc.

Našli jste v článku chybu?

13. 1. 2004 22:16

Bohouš (neregistrovaný)

Souhlasím s vámi, že statická (compile time) typová kontrola nesnižuje množství chyb a neurychluje vývoj (nejraději používám jazyk Smalltalk). Rychlost vývoje spíše zpomaluje. Stejně člověk musí testovat. A chyby v záměnně typů jsou velmi snadno odhalitelné a opravitelné. Nadruhou stranu se domnivám že pro dokumentační účely a refaktoring se může hodit.

20. 12. 2003 12:37

Pavel Kolesnikov (neregistrovaný)

Prijde mi, ze vsehna povidani o novinkach v 1.5 se vyzivaji v legrackach jako typovane seznamy a nova syntaxe cyklu, ale natolik zasadni novinku, jako je zavedeni syntaxe pro metadata, kazdy zlehka natukava jedinym a tim samym vagnim prikladem na webove sluzby.

Vitalia.cz: Znáte „černý detox“? Ani to nezkoušejte

Znáte „černý detox“? Ani to nezkoušejte

Root.cz: 250 Mbit/s po telefonní lince, když máte štěstí

250 Mbit/s po telefonní lince, když máte štěstí

Podnikatel.cz: Přehledná titulka, průvodci, responzivita

Přehledná titulka, průvodci, responzivita

Lupa.cz: Avast po spojení s AVG propustí 700 lidí

Avast po spojení s AVG propustí 700 lidí

Podnikatel.cz: Víme první výsledky doby odezvy #EET

Víme první výsledky doby odezvy #EET

Vitalia.cz: Paštiky plné masa ho zatím neuživí

Paštiky plné masa ho zatím neuživí

Podnikatel.cz: Na poslední chvíli šokuje výjimkami v EET

Na poslední chvíli šokuje výjimkami v EET

Podnikatel.cz: Podnikatelům dorazí varování od BSA

Podnikatelům dorazí varování od BSA

Vitalia.cz: Dáte si jahody s plísní?

Dáte si jahody s plísní?

Podnikatel.cz: Chtějte údaje k dani z nemovitostí do mailu

Chtějte údaje k dani z nemovitostí do mailu

Vitalia.cz: Chtějí si léčit kvasinky. Lék je jen v Německu

Chtějí si léčit kvasinky. Lék je jen v Německu

Měšec.cz: U levneELEKTRO.cz už reklamaci nevyřídíte

U levneELEKTRO.cz už reklamaci nevyřídíte

120na80.cz: Horní cesty dýchací. Zkuste fytofarmaka

Horní cesty dýchací. Zkuste fytofarmaka

Root.cz: Vypadl Google a rozbilo se toho hodně

Vypadl Google a rozbilo se toho hodně

Lupa.cz: UX přestává pro firmy být magie

UX přestává pro firmy být magie

Vitalia.cz: Mondelez stahuje rizikovou čokoládu Milka

Mondelez stahuje rizikovou čokoládu Milka

Lupa.cz: Insolvenční řízení kvůli cookies? Vítejte v ČR

Insolvenční řízení kvůli cookies? Vítejte v ČR

Vitalia.cz: Jsou čajové sáčky toxické?

Jsou čajové sáčky toxické?

Vitalia.cz: To není kašel! Správná diagnóza zachrání život

To není kašel! Správná diagnóza zachrání život

DigiZone.cz: Sony KD-55XD8005 s Android 6.0

Sony KD-55XD8005 s Android 6.0