Pri behu programu môže dochádzať ku chybám. Chyby môžu byť spôsobené samotným programom (zle napísaným kódom) alebo jeho okolím (užívateľom, systémom…).
Zatiaľ čo chyby programu by sme mali opraviť v samotnom kóde, chyby spôsobené okolím sú mimo nás a nedokážeme im zabrániť (užívateľ môže zadať nezmyselný vstup, neexistujúci súbor, odpojiť tlačiareň…).
Ošetrenie chýb okolia
Hoci chybám okolia nedokážeme zabrániť, mali by sme ich aspoň predvídať a zraniteľné miesta ošetriť. Zoberme si nasledujúci učebnicový príklad delenia s nulou:
class MathLibrary { public static int division(int x, int y) { return x/y; } … }
Ak užívateľ zadá y = 0
, tak program spadne. Podľa správnosti by však program mal užívateľa na chybu upozorniť. Preto by sme mali zraniteľné miesto ošetriť pomocou podmienky alebo ešte lepšie pomocou mechanizmu výnimiek.
Ošetrenie chyby pomocou podmienky
Prvé čo nás napadne je skontrolovať, či druhý parameter nie je nula:
if (y != 0) System.out.println(MathLibrary.division(x, y)); else System.out.println("Can't divide by zero!");
Nevýhoda tohto riešenia je zjavná, a síce pri každom volaní metódy s delením, je nutné kontrolovať jej druhý parameter. Logicky sa teda núka myšlienka, presunúť kontrolu parametru priamo do metódy delenia. Narazíme však na nový problém, akú hodnotu vrátime, ak bude druhý parameter nula? Návratová hodnota je v tomto prípade int
a každá jeho hodnota môže predstavovať správny výsledok. Riešením by bolo použitie objektového typu Integer
, ktorý môže obsahovať aj hodnotu null
, avšak existuje aj krajšie riešenie, a tým sú výnimky.
Ošetrenie chyby pomocou výnimky
Výnimky predstavujú mechanizmus, ktorý umožňuje písať spoľahlivé programy odolné voči chybám okolia. Nebezpečnú časť kódu môžeme uzavrieť do try
bloku, ktorý predstavuje akýsi chránený režim, v ktorom ak dôjde ku chybe, resp. správnejšie ku výnimke, môžeme ju odchytiť a spracovať v catch
bloku:
try { System.out.println(MathLibrary.division(x, y)); } catch (Exception e) { System.out.println("Division failed!"); }
Ak delenie prebehne v poriadku, vykoná sa celý try
blok a catch
blok sa preskočí, avšak ak sa vyskytne akákoľvek chyba, vykonávanie try
bloku sa zastaví a vykoná sa catch
blok.
Výhodou je, že nemusíme ošetrovať konkrétne parametre (niekedy je potrebné ošetrovať veľké množstvo parametrov), pokiaľ dôjde k akejkoľvek chybe, zachytíme ju. To však nie je všetko, predtým ako prídeme k ďalším výhodám výnimiek, poďme sa na ne bližšie pozrieť.
Hierarchia výnimiek
Výnimky sú z technického hľadiska objekty, ktoré sú vytvárané a vyvolávané príkazom throw
:
- automaticky behovým systémom Javy, ak nastane nejaká behová chyba (napr. delenie nulou),
- samotným programom, ak zdetekuje nejaký chybový stav, na ktorý je potrebné reagovať (napr. do metódy príde zlá hodnota parametru).
Objekty výnimiek môžu mať rôzne typy v závislosti na chybe, ku ktorej došlo, všetky sa však odvíjajú od nasledujúcej hierarchie:
java.lang.Throwable
– supertrieda všetkých chýb a výnimiek v Jave, iba objekty oddedené od tejto triedy môžu byť vyhodené príkazomthrow
,java.lang.Exception
– tzv. strážené výnimky, teda výnimky, ktoré musia byť ošetrené, inak spôsobia pád programu, používajú sa najčastejšie,java.lang.RuntimeException
– tzv. behové alebo nestrážené výnimky, nespôsobujú pád programu, a teda nemusia byť zachytávané,java.lang.Error
– vážne chyby JVM, ktoré väčšinou nedokážeme spracovať a končia pádom programu (napr. Out of memory, Stack Overflow…)
Táto hierarchia môže slúžiť ako základ pre budovanie tried vlastných výnimiek, býva zvykom názvy takýchto tried končiť slovom Exception
. Vlastné výnimky slúžia na označenie špecifických chýb, ktoré plynú z problému, ktorý program rieši.
Syntax výnimiek
try { code } catch (ExceptionType name) { exception handling } finally { cleanup code }
Blok try
:
- slúži na uzatvorenie kódu, v ktorom môže dochádzať k výnimkám, tento kód môže obsahovať jeden alebo viac príkazov.
Blok catch
:
- nie je povinný, no môže ich byť aj viac,
- slúži na odchytenie výnimiek, ktoré môžu vzniknúť v bloku
try
, - v zátvorke je uvedený typ výnimky (resp. jej predka), ktorý sa v danom bloku rieši (od Java 7 tu môže byť uvedených aj viac typov oddelených znakom
|
), - ak uvedieme viac blokov
catch
, ide o tzv. kaskády, resp. reťazenie výnimiek, výnimka je zachytená v najbližšom vhodnom blokucatch
, preto ak v blokutry
vznikajú rôzne výnimky z jednej hierarchie tried, je vhodné uvádzať najprv špeciálnejšie a až potom všeobecnejšie typy.
Blok finally
:
- nie je povinný,
- vykoná sa vždy na záver, či už sa blok
try
dokončí alebo nie, a to aj prípade, že v ňom bude príkazreturn
, - táto vlastnosť sa s výhodou využíva na uvoľňovanie systémových zdrojov (zatváranie súborov, uvoľňovanie pamäte…),
- od Java 7 existuje tzv.
try-with-resources
, ktorý vygeneruje blokfinally
automaticky v prípade, že v blokutry
používame nejaké zdroje a automaticky ich tu uvoľní (pozor, blokcatch
sa automaticky negeneruje).
Prepúšťanie výnimiek
Veľkou výhodou výnimiek je, že dovoľujú chybu riešiť na inom miesto, ako na tom, kde na ňu narazíme. Niekedy totiž chybu ani nie je možné riešiť tam, kde nastala a musíme sa vrátiť „vyššie“.
Ak vyhodený objekt výnimky nezachytíme v bloku catch
v rámci metódy, kde vznikol, výnimka prejde do nadradenej (volajúcej) metódy. Takto môže postupovať, až kým niekde nie je zachytená. V opačnom prípade beh JVM skončí s hlásením o výnimke.
Ak výnimku chceme prepustiť do nadradenej metódy a ide o stráženú výnimku, je potrebné za názov metódy pridať kľúčové slovo throws
a typ výnimky, napr.:
public boolean saveSettings(String fileName) throws IOException { … }
Výnimku môžeme vyhodiť aj explicitne príkazom throw
:
if (fileName == null || fileName.isEmpty()) throw new IOException();
Pravidlá pre výnimky
- Na výnimku vždy reagujeme, nepotláčame ju, t. j. blok
catch
nenechávame nikdy prázdny (minimálne zavolámee.printStackTrace()
). - Výnimkami nikdy nepokrývame chyby samotného programu, to je hrubé zneužitie.
- Do bloku
try
umiestňujeme len rizikový kód, nie celý program. - Ak sa na výnimku nedá vhodne reagovať na miest, treba ju prepustiť vyššie.
Zdroje
- David Čápka: 1. díl – Výjimky v Javě (12. 4. 2016)
- doc. RNDr. Tomáš Pitner, Ph.D.: Kurz PB162 – Výjimky (12. 4. 2016)
Ahoj. Zaujmalo by ma ako je to s uvolnovanim zdrojov ( otvoreneho suboru, ci velkym polom (v cykle vytvaram spajany zoznam pri citani zo suboru o velkosti 100MB ,…) v pripade exception neocakavana hodnota tak je automaticky zruseny ten zoznam a subor zavrety?? Ak je napr. na vyssej urovni odchytena exception. Alebo sa o uzavretie suboru musim postarat v nizsej urovni cez finally ? A to iste ohladom udajovych struktur vzniknutych/modifikovanych v try casti.. ci ich java sama automaticky odstrani ako odpad. Otazka smeruje k tomu ako pracuje garbage collector v pripade vynimky.
Ahoj, garbage collector síce po čase odstráni objekty, na ktoré neexistuje odkaz, ale nemusí to spraviť okamžite. V prípade premenných je nám to jedno, no v prípade napr. súborov sa môže stať, že sa pokúsime po vyskočení výnimky súbor znovu otvoriť a nebude to možné, lebo bude blokovaný. Preto je dobré sa pokúsiť takéto resources v bloku finally vždy uzavrieť, prípadne ešte šikovnejšie využiť try-with-resources (od Javy 7), ktorý sa o to postará za nás.