<< Java Collections Framework | Obsah | Statické metódy a premenné, konštanty >>
Už vieme, že pri volaní nejakej metódy môže nastať za istých okolností nejaká výnimka. Keď ju neodchytíme, program nám skončí a vypíše sa stack trace.
Tiež vieme, že výnimky, ktorým vieme zabrániť overovaním vhodných podmienok pred spustením kritickej operácie neriešime odchytávaním, ale práve overovaním podmienok vopred.
Výnimky, ktorým nevieme predchádzať overovaním vstupných podmienok môžeme odchytávať v try-catch-finally blokoch.
Výnimky sa používajú pri výnimočných stavoch, s ktorými si daná metóda nevie poradiť. Ak si metóda vie poradiť sama, nemá zmysel výnimku vyhadzovať. Metóda vyhadzuje výnimku s tým, že je šanca, že sa s tým kus kódu, ktorý danú metódu volal, vie vysporiadať. Dôvodom na vyhodenie výnimky je väčšinou neschopnosť vykonať požadovanú akciu a vrátiť požadovanú hodnotu. Príčinou môže byť:
Doteraz, sme pracovali iba s výnimkami, ktoré hádzali cudzie metódy. Výnimky môžeme vyhadzovať aj zo svojich metód. Môžeme vyhadzovať už známe výnimky (java.lang.NumberFormatException
, java.lang.IllegalArgumentException
, ...) alebo si môžeme vyrobiť aj vlastné výnimky. Výnimky sú obyčajné triedy, ktoré dedia od triedy java.lang.Exception
. Môžu teda mať vlastné inštančné premenné, metódy a konštruktory. Dokážeme tak odoslať veľmi komplexnú informáciu o vzniknutej sitácii, a nezriedka aj návrh na riešenie. Vyrobme si vlastnú výnimku ZápornýVstupException
.
Vyrobme si príklad, ktorý počíta faktoriál čísla n
. Ak príde n
záporné, vyhodíme výnimku ZápornýVstupException
.
Všimnite si, že výnimka je obyčajný objekt, vytvorený cez new. Pomocou kľúčového slova throw okamžite ukončíme beh metódy a vyhodíme výnimku. Výnimky, ktoré môžeme vyhadzovať, uvádzame v hlavičke za kľúčovým slovom throws. Ak môžeme vyhadzovať viac druhov výnimiek, oddeľujeme ich čiarkami.
Keď teraz budeme volať túto metódu, Java od nás bude požadovať ošetrenie výnimky. Zabalíme teda toto volanie do try-catch blokov.
Aby mohli bez ujmy na zdraví používať vaše programy aj iní ľudia (resp. po dlhšom čase aj vy sami), nikdy nevyhadzujte všeobecné výnimky, napr:
Ak nastane všeobecná výnimka, používateľ vášho kódu nemusí pochopiť, a obvykle ani nepochopí, čo sa stalo a čo bolo príčinou. Na druhej strane, keď dostane výnimku s rozumným menom, napr. ZápornýVstupException
, už z jej názvu vie veľmi rýchlo pochopiť, že problém zrejme spočíva v záporných vstupoch.
Ak výnimku spôsobilo volanie inej metódy, máme dve možnosti:
V druhom prípade neošetrenú výnimku uvedieme v throws
a necháme vyššie metódy nech to riešia. Skôr či neskôr sa však toto prehadzovanie zodpovednosti za ošetrenie vzniknutého stavu musí niekde zastaviť -- v opačnom prípade výnimka vybuble von z metódy main()
, čo vyústi v násilné ukončenie programu. Napríklad, keby sme pri čítaní súboru neodchytili FileNotFoundException
mohli by sme ju proste nechať vybublať vyššie:
Obveľa zaujímavejšou možnosťou je prebaliť výnimku do novej, popisnejšej. Vytvoríme si novú výnimku PocitadloException
, ktorej cez konštruktor vložíme textový popis a aj pôvodnú výnimku FileNotFoundException
, ktorú prebaľujeme. Ten, čo odchytí výnimku má tak lepšiu informáciu o tom, čo sa stalo, pretože má vlastne popisnú výnimku aj pôvodnú výnimku v jednom. Konštruktory triedy PocitadloException
si vygenerujeme cez Source > Generate Constructors From Superclass.
Prebaľovanie výnimiek má zmysel aj preto, že používateľ cudzieho kódu môže mať s nízkoúrovňovými výnimkami, vybublanými z vnútra programu, problém. Má pochopiť, čo sa stalo, a hlavne čo bolo príčinou a ako to treba riešiť. Predstavte si, že vám cudzí modul vyhodí napr. výnimku NumberFormatException
a vy ste pritom volali metódu na poslanie matice na server a nevidíte súvis.
Príklad správneho prebaľovania výnimiek bublacúcich z vnútra nejakého projektu:
VSúboreChýbaPremennáException: početBalíčkov
KonfiguráciaNemáPremennúException: početBalíčkov
ModulKonfiguráciaOutOfDateException
StarrýJarSúborException: konfiguracia.jar
Keby sa k vám dostala prvá výnimka, neviete čo s tým. Ale na základe poslednej už viete, že potrebujete zohnať novší modul konfigurácie.
Už vieme, že v Jave sú dva druhy výnimiek - kontrolované a nekontrolované. Okrem nich môžu metódy vyhadzovať aj chyby.
try-catch
blokoch alebo nechať vybublať do volajúcej metódy. Ak sa kontrolované výnimky vyhadzujú, musia sa uviesť v hlavičke za throws
.
RuntimeException
. Nemusia odchytiť a nemusia sa uvádzať v throws
.
Error
. Podobne ako nekontrolované výnimky sa nemusia odchytávať a uvádzať v throws
.
Chyby (Errors
) predstavujú taký stav systému, z ktorého sa aplikácia nemá šancu zotaviť. Väčšinou ju vyhadzujú nízkoúrovňové metódy z vnútra Javy (bežný programátor ich nevyhadzuje), a predstavujú fatálne chyby, z ktorých je nemožné sa zotaviť. Príkladmi sú OutOfMemoryError
-- indikácia vyčerpanej pamäte alebo VirtualMachineError
, keď virtuálny stroj nevie púšťať Java programy.
Otázka, kedy použiť kontrolovanú a kedy nekontrolovanú výnimku, nie je jednoduchá. Sú vývojári, ktorí nepoužívajú kontrolované výnimky vôbec (iné OOP jazyky kontrolované výnimky nemajú). Nekontrolované výnimky majú tú nevýhodu, že zvádzajú k lenivosti. Dobrý programátor, keď hádže nekontrolované výnimky, uvádza ich, napriek tomu, že to nie je nutné, aj do dokumentácie aj do throws
.
Výhodou nekontrolovaných výnimiek je, že ak sme si istí, že sme dodržali kontrakt a nenastanú výnimky identifikujúce nedodržanie kontraktu, nemusíme ich odchytávať v try-catch
blokoch. Ak sa naviac vyhadzované nekontrolované výnimky uvedú do dokumentácie a do throws
, nenastane situácia, že nejaká neočakávaná (nezdokumentovaná) výnimka vám vybuble z vnútra cudzieho kódu. Takéto situácie sú veľmi nepríjemné a spôsobujú často mnohé hodiny tápania a hľadania príčiny. Uvedenie v throws
je to najmenej, čo môžeme urobiť, aj keby sme dokumentáciu ešte nemali dotiahnutú. Je to aspoň informácia o možnom probléme. Naviac vďaka throws
vám Eclipse vie sám generovať try-catch
bloky.
Pri kombinácii výnimiek a dedičnosti si treba dávať pozor. Platí totiž, že prekrývajúce metódy môžu vyhadzovať iba také výnimky ako ich náprotivky v rodičovských triedach. Inými slovami, ak chceme v oddedenej triede v nejakej metóde vyhadzovať viac výnimiek ako vyhadzuje metóda ktorú prekrývame, musíme rozšíriť zoznam výnimiek v throws
pôvodnej rodičovskej metódy.
Napríklad ak metóda dajUmiestnenie()
triedy Film
vyhadzuje výnimku FilmException
:
tak v metóde dajUmiestnenie()
triedy FilmNaDvd
, ktorá dedí od triedy Film
môžeme vyhadzovať iba FilmException
, jej potomkov alebo nič. Napríklad nasledujúci kód by nefungoval:
Možné opravy:
Táto oprava je však pomerne nešťastná. Všeobecný Film
by totiž nemal mať do činenia s výnimkami, ktoré zväzujú triedu Film
u s jej potomkom DVD.
I v tomto prípade je oprava netradičná. Hádzanie všeobecnej výnimky Exception
sme totiž vyššie v texte vyhlásili za nevhodné.
V tomto konkrétnom príklade je ideálne, keď DVDException
oddedí od FilmException
, a metóda dajUmiestnenie()
bude môcť vyhodiť DVDException
bez toho, aby narušila podmienku v rodičovi.
Už vieme že odchytávanie výnimiek sa deje v catch
blokoch. Tiež vieme, že catch
blokov môže byť viac. Fungovanie viacerych blokov je založené na tom, že výnimky odchytí prvý catch
blok, ktorý vie prijať vyhodenú výnimku. V každom catch
bloku sa uvádza ako parameter nejaká premenná, ktorej typ určuje, či sa výnimka odchytí alebo nie.
Na to, aby sme vedeli, ktoré výnimky budú odchytené ktorým catch
blokom, potrebujeme poznať hierarchiu dedenia vyhadzovateľných (Throwable
) objektov.
Už vieme, že z premennej nadradenej triedy vieme referencovať ľubovoľný objekt potomka tejto triedy. Tento vzťah platí aj vo svete výnimiek. To znamená, že ak v prvom catch
bloku odchytávame výnimku do premennej typu Exception
tak odchytíme ľubovoľnú výnimku. Jediné čo by sa neodchytilo by boli chyby. Z toho vyplýva, že keby sme v ďalších catch
blokoch odchytávali napríklad výnimku FileNotFoundException
, ktorá je potomkom triedy Exception
tak, by sme ju týmto catch blokom nikdy neodchytili, pretože by bolá odchytená prvým catch
blokom s typom Exception
. catch
bloky teda uvádzame vždy od najkonkrétnejších po najvšeobecnejšie.
Príklad správneho poradia odchytávania:
Exception
.
try-catch
bloky, throws
je rýchlejšie napísané"