Výjimky
Výjimky
Zopakujte si, co jsou výjimky, jak se chovají a jakými příkazy se s nimi zachází (try, catch, throw).
Význam výjimek
Na výjimku nahlížejte jako na součást kontraktu metod.
Tvoříme-li metodu, děláme tím kontrakt s jejími uživateli o tom, jaké budou parametry a jaký bude jejich význam; jakou vrátíme návratovou hodnotu a jaký bude její význam; jaké budou side-effects volání apod. A stejně tak je součástí tohoto kontraktu, jaké výjimky mohou vzniknout a jaký budou mít význam.
V určitých případech je možné určité tzv. runtime výjimky považovat za implicitní součást kontraktu (tj. ani nepopsanou v Javadocu). Například:
/**
* Find student by their UID.
* @param uid UID of the student. Never null.
* @return the student name or null if not found.
*/
public String findStudentNameByUid(String uid);
Tato metoda ve svém kontraktu říká, že očekává řetězec uid, který nesmí být null. Pokud by došlo k volání s parametrem null, nemělo by překvapit, že to dopadne vyhozením NullPointerException, přičemž implementátor ani nemusel explicitně kontrolovat ne-null-ovost argumentu, NullPointerException prostě vznikne někde v implementaci metody při prvním použití uid.
Poznamenejme ale, že takovýto implicitní kontrakt se v praxi týká vlastně jen uvedené NullPointerException (null tam, kde nesmí být), případně IndexOutOfBoundsException (do metody vstupuje číselný index mimo rozsah), IllegalArgumentException (rozpor s požadavky na parametry) a IllegalStateException (rozpor volání, parametrů a vnitřního stavu objektu). U posledních dvou je však běžné požadavky popsat v Javadocu; implicitní může být spíš jen typ výjimky, na niž chybné chování povede.
Druhy výjimek v Javě
Vše, co jde v Javě "vrhnout", je potomkem třídy Throwable. Nás zajímá zejména strom podtříd třídy Exception. V Javě rozlišujeme dva typy výjimek:
- Tzv. "checked" exceptions, což je třída Exception a všechni její potomci kromě třídy RuntimeException. Používáte-li kód, který deklaruje vrhání takové výjimky, compiler Vás bude nutit tuto výjimku ošetřit (try + catch) nebo u svého kódu (metody) deklarovat, že tuto výjimku může vrhnout (klíčové slovo throws v deklaraci - přenášíte tím zodpovědnost za ošetření výjimky na volajího). Checked exception samozřejmě můžete vrhnout i sami, pak musí být v deklaraci Vaší metody.
- Tzv. "unchecked" nebo též runtime exceptions, což je třída RuntimeException a její potomci. Používáte-li kód, který může takovou výjimku vrhnout, můžete a nemusíte ji ošetřit. A můžete a nemusíte ji uvést v deklaraci metody (throws). Pokud jde o svým významem zásadní výjimku pro danou metodu, je slušnost ji uvést v deklaraci throws a samozřejmě také v Javadoc.
Ošetření výjimek a výjimečných stavů vs. kontrakt metody
Řekněme, že v určitém místě kódu může vznikat výjimka (ať už checked, kde Vás na to upozorní kompilátor, nebo unchecked, kde si potřebu jejího ošetření musíte odvodit z kontraktu použité metody).
- Výjimku chytíte a v jejím ošetření pokračujete "jinak". Samotné zachycení výjimky tak pro má význam if - else. Nejde o nejběžnější případ. Příklad použití: pomocí konstrukce třídy URL testuji, zda má řetězec formát URL.
- Výjimku nebudeme ošetřovat, prostě vyletí "výš" (v případě checked výjimek nutno přidat do deklarace). Tento případ má smysl jen a pouze tehdy, když jde o výjimku, která je součástí kontraktu Vaší metody. Jinými slovy - pokud vůbec dává ve své podobě volajícímu smysl. Například: vytvářím komponentu pro zjišťování cen akcií, která načítá data z XML. Pro čtení se používá SAX, může vzninout SAXParseException. S touto výjimkou však nemůžeme obtěžovat uživatele naší komponenty pro informace o akciích - je to naš interní věc a nikdo z venku jí nerozumí. Místo toho, je potřeba na stav reagovat dle bodu 3.
- Chytíme výjimku jednoho typu, vyhodíme výjimku jiného. Jde o poměrně běžnou situaci, kdy místo našeho interního problému reportujeme ven problém, jemuž volající rozumí. V uvedeném příkladu bychom tak nenechali "vyletět" SAXParseException, ale deklarovali bychom si vlastní - např. StockDataAccessException, a tu bychom vyhodili. Běžné je původní výjimku dávat jako parametr k nové výjimce pro případ diagnostiky. Proto mají výjimky jako parametr konstruktoru jinou výjimku.
Příklad kódu s vyhozením jiné výjimky:
try {
…
} catch (SAXParseException e) {
throw new StockDataAccessException(e)
}
Vyhazování výjimek se samozřejmě nemusí dít jen, pokud chytíme cizí výjimku. Výjimečný stav můžeme detekovat jinými prostředky jazyka a vyhozením výjimky reagovat. Např.:<-p>
if (!stockExchangeDataSource.isOnline()) {
throw new StockDataAccessException("Not online.")
}
Co rozhodně nedělat
- Ne: "požírání" nesouvisejících výjimek
- Ne: zahození
- Ne: "ošetření" pomocí printStackTrace() a následné porušení kontraktu (k čemuž může svádět to, "co jste se učili").