30. prosince 2007

Nová Java: Chceme ji? - podruhé

Blíží se nám konec roku 2007 tak proč nepokračovat v mých představách změny jazyka, tj. takový malý výhled toho co by se v roce 2008 mohlo změnit.

V předchozím příspěvku jsem se rozjel na téma vylepšení programovaní paralelních programů. Dneska se pustím spíš do oblasti vylepšení psaní kódu, tj. změny, které cítím, že zpřehlední kód.

Vylepšení příkazu switch

Jistě všichni víme, že příkaz switch funguje pouze nad typy int a char. Takže žádný long, fload, double, natož např. String (o tomto konkrétním typu hovořili pánové v JavaPosse #151). Kdo se podíval pod pokličku překladu switch příkazu do bytecodu, zjistil, že k tomu slouží dvě instrukce, buď lookupswitch nebo tableswitch. V podstatě jde o to, že tato instrukce na základě hodnoty výrazu ve switch příkazu skočí na správný case. Pokud chceme vytvořit ekvivalent např. pro String, pak použijeme kaskádu if - else if - else if ... .
Proč, ale nepovolit switch i pro jiné výsledné typy výrazu (např. String) a až překladačem vše přeložit stejně jako onu kaskádu ifů?

Odchytávání více typů výjimek najednou při zachování typu výjimky

I v tomto případě jsem se nechal inspirovat povídáním pánů z JavaPosse. No představme si následující metodu:
protected Object invokeMethod(String s) throws IllegalArgumentException, 
IllegalAccessException, InvocationTargetException,
SecurityException, NoSuchMethodException
{
Method m = String.class.getMethod("toString", new Class[0] );
return m.invoke(s, new Object[0]);
}
Nyní chceme tuto metodu zavolat a vyhodit jinou výjimku v případě, že se nepodařilo najít metodu a jinou v případě, že se ji nepodařilo zavolat. Nemáme jinou volbu než:
try {
invokeMethod("A");
} catch (IllegalArgumentException e) {
throw new CantInvokeException(e);
} catch (IllegalAccessException e) {
throw new CantInvokeException(e);
} catch (InvocationTargetException e) {
throw new CantInvokeException(e);
} catch (SecurityException e) {
throw new CantFindMethodException(e);
} catch (NoSuchMethodException e) {
throw new CantFindMethodException(e);
}
A to je vskutnu zvěrstvo. Nebylo by přeci jenom lepší něco následujícího:
try {
invokeMethod("A");
} catch (IllegalArgumentException, IllegalAccessException,
InvocationTargetException: Exception e) {
throw new CantInvokeException(e);
} catch (SecurityException, NoSuchMethodException: Exception e) {
throw new CantFindMethodException(e);
}
Máme možnost specifikovat libovolný počet typů vyjímek, které chceme odchytit jediným catch příkazem, za seznam přidáme dvojtečku a specifikujeme typ proměnné (v našem případě je nejbližším společným předkem výjimek typ Exception, který je zároveň typem proměnné e). Kloním se k variantě, že programátor musí specifikovat typ proměnné, nenechával bych to na překladači.
Co znamená dodatek při zachování typu výjimky z názvu kapitoly? Pokud bychom kód z předchozího úryvku změnili na:
try {
invokeMethod("A");
} catch (IllegalArgumentException, IllegalAccessException,
InvocationTargetException: Exception e) {
//něco
throw e;
} catch (SecurityException, NoSuchMethodException: Exception e) {
//něco jiného
throw e;
}
a uvedli ho v metodě se stejnou klauzulí throws jako má metoda invokeMethod, pak bude vše v pořádku. Překladač se nebude zlobit, že se snažíme vyhodit výjimku typu Exception z metody, která ji nemá uvedenou ve throws, protože proměnná e je typu Exception, ale může nabývat pouze hodnot typů uvedených před dvojtečkou. Samozřejmě musí být final (není tak uvedená, ale je).

Závěr

Obě tyto vychytávky rozhodně nezesložití jazyk, a když tak jenom nepatrně. Ale přinesou bezesporu větší čitelnost řadě algoritmů. To je přínos, který jednoznačně stojí minimálně za zamyšlení.

12 komentářů:

LP řekl(a)...

Příkaz switch pracuje ještě s typem Enum (od 1.5). V mnoha případech, kdy je potřeba switch pro řetězce, lze seznam přípustných hodnot nadefinovat jako typ Enum, což je bezpečnější cesta. Někdy je to za cenu napsání několika znaků navíc.

V některých případech je vhodné nahradit příkaz switch (či sekvenci příkazů if) vhodným návrhovým vzorem.

jira řekl(a)...

S tím typem enum je to jasné (z hlediska překladače se enum převede na int).

Ne vždy je ale možné použít návrhový vzor a nebo enum (např. mám-li na vstupu řetěz a potřebuju jej nějak zpracovat ...).

Tomas řekl(a)...

S tym switch , case to bude asi reminiscencia z cc,cpp v tychto jazykoch ide pri switch, case o tvrdy (rychly) skok ako je goto a navestie koli tomu sa podla mna porovnavaju len numericke premenne.

Daniel řekl(a)...

Ja si nastesti v Pythonu zvykl SWITCH vubec nepouzivat, k cemu taky :-)

jira řekl(a)...

to Tomas> Switch je samozřejmě v byte-kódu realizován pomocí instrukcí, které dostávají návěští kam se má skákat. Proč se jedná v podstatě pouze o int (protože enum je převáděn na int a char pravděpodobně taky) je jasné, instrukce je jednoduchá.
Kdyby již od začátku vše nebylo řešeno speciální instrukcí ...

to daniel> A jak v Pythonu řešíte problém, který je jako ušitý pro switch?

Daniel řekl(a)...

to jira> vsechno resim dvojici IF a ELIF, jinak to nejde SWITCH tam vubec neni (i kdyz se da trosku simulovat pomoci dictionariy a lambda fce). Pri pythoni syntaxi je to reseni, ktere se vyrovna prehlednosti SWITCHe v Jave.

Myslel jsem to spis tak, ze v Pythonu jsem si na to zvyknul, takze ani v Jave ten switch moc nepouzivam. I kdyz chapu, ze v jistych pripadech to muze znamenat rychlostni rozdil.

Zdeněk Troníček řekl(a)...

Odchytávání více vyjímek najednou umí už prototyp Javy 7. Syntaxe je tato:

try {
...
} catch (IOExcetion | SecurityException e) {
e.printStackTrace();
}

Jinak asi největší chystanou změnou jsou closures. Pokud vás zajímá, jak možná bude vypadat Java na konci roku 2008, na http://tronicek.blogspot.com je pár příkladů na closures.

jira řekl(a)...

to zdeněk troníček> Ano, ale bohužel nikde není oficiálně deklarováno, že to bude doopravdy :-( Ovšem o tom, že se na této vlastnosti již pracuje jsem netušil ...

Jinak closures jsou hodně zajímavé a mluví se o nich již dlouho, takže v mém seznamu nebudou ...

benzin řekl(a)...

Switch pokud vim, tak v good practice se doporucuje uplne switchi vyhnout. Tvrdi se ze tam kde je switch tak tam to hovori o spatnem navrhu. Switch pro string nema vyznam v dobe kdyz mate Enum. Vytvorit Enum tak aby pomoci staticke metody enumByStr vratil enum podle jeho jmena je trivialitka. Pak uz muzete pracovat rovnou s hodnotou vyctu. V opacnem pripade totiz ziskate magicke retezce, neco na zpusob magickych cisel.

Odchytnuti vyce vyjimek najednou? To je problem?
catch (Exception e) {
if (e instanceof IllegalArgumentException || atd.
} else { throw e; }
}

Vzhledem k tomu jak malo je to zapotrebi a k tomu, ze se spise smeruje k Runtime vyjimkam mi to prijde jako zbytecne bobtnani jazyka.

jira řekl(a)...

to benzin> Existují přípdy, kdy je možné se zbavit switche úplně - do mapy si jako klíče dám stringy a hodnoty budou commandy, které chci provést. Ovšem jsou situace, kdy to není možné, ani jednoduše proveditelné, bohužel .. není jich mnoho ... jazyk by zůstal stejný, jenom by se v knížkách o switchi řeklo, že je možné jej provádět nad čímkoliv a překladač by se postaral.

Co se vámi navrhovaného kódu k výjimkám týče, pak je nefunkční, protože throw e; neprojde u překladače. Metoda v klauzuli throws neuvádí Exception, ale právě IllegalArgumentException, ...

Mým cílem je, aby programy mnou napsané byly přehledné, lehce čitelné a co možná nejkompaktnější. Vše o čem píši se za léta mého programování ukázalo jako problém, který bych rád vyřešil. Koneckonců jak jsem se díky pobídce p. Troníčka dozvěděl, výjimky i switch pro řetězce jsou v seznamu změn pro Java7.

benzin řekl(a)...

4Jirka: No projde. Exception je rodicem kazde vyjimky tudiz ten blok zachytne uplne jakoukoli vyjimku i tu Runtime a pak ji opet zpatky vyhodi v pripade ze nebyla zpracovana.

Ale musim rict, ze to se opravdu stava tak strasne malo, ze kvuli tomu delat novou upravu jazyka je dle meho opravdu zbytecne.

Switch s jeho breaky je skutecne relik jazyka C. Nemyslim ze ma co delat v Jave, no je tam, ale proc ho rozsirovat? Ani nepamatuju kdy sem ho pouzil naposled.

jira řekl(a)...

to benzin> Problem není v odchycení, ale v opětovném vyhození výjimky. Zkus si následující příklad:

public class X {

protected void x1() throws IllegalAccessException, IllegalArgumentException, SecurityException {
System.out.println("A");
}

protected void x2() throws IllegalAccessException, IllegalArgumentException, SecurityException {
try {
x1();
} catch (Exception e) {
e.printStackTrace();
throw e;
}
}

}