Java – výnimky

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:

Exception_hierarchy

  • 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íkazom throw,
  • 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 bloku catch, preto ak v bloku try 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íkaz return,
  • 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 blok finally automaticky v prípade, že v bloku try používame nejaké zdroje a automaticky ich tu uvoľní (pozor, blok catch 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áme e.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

 

3 komentáre

  1. 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.

    1. 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.

Leave a Reply

Vaša e-mailová adresa nebude zverejnená. Vyžadované polia sú označené *