13. Výjimky
Výjimka (exception) je definována jako událost, která nastane
během provádění programu a která naruší normální běh instrukcí. Výjimka je
vyvolána například při chybném otevření souboru, při překročení mezí
pole, při aritmetické chybě apod. Mechanismus výjimek umožňuje tyto chybové
stavy zachytit a ošetřit. K tomu slouží dvojice bloků try
a catch, jejichž syntaxe je následující:
try {
// hlídaný blok
} catch( třídaVýjimek1 jménoProměnné1 ) {
// ošetření výjimky
} catch( třídaVýjimek2 jménoProměnné2 ) {
// ošetření výjimky
}
- Hlídaný blok try - uzavírá kritickou část programu, kde
"může" být vyvolána výjimka.
- Záchytné bloky catch - ošetřují výjimky, musí následovat
bezprostředně za blokem try a může jich být libovolný počet.
Vyvolaná výjimka je objekt a každý záchytný blok zachycuje jeden typ výjimek (třídaVýjimek1, třídaVýjimek2). Na vyvolanou
výjimku v záchytném bloku odkazuje referenční proměnná (jménoProměnné1,
jménoProměnné2). Pokud v hlídaném bloku výjimka:
Priklad 13.1. |
Metoda nactiBajt otevře soubor zadaného jména (1) a
načte z něj první byte, který vypíše na standardní výstup (1) (2).
void nactiBajt(String jSouboru) {
try {
FileInputStream soubor = new FileInputStream(jSouboru); // (1)
System.out.println(soubor.read()); // (2)
} catch(FileNotFoundException e) { // (3)
System.out.println("Soubor " + jSouboru + "nenalezen."); // (4)
} catch(IOException e) { // (5)
System.out.println("Chyba při čtení souboru " + jSouboru);// (6)
}
}
Při pokusu otevření souboru se může stát, že tento nebude nalezen
(nastane výjimka FileNotFoundException), např. pokud soubor
neexistuje nebo je chybně zadané jméno. Pokud tento výjimečný stav
nastane, je zachycen blokem catch na řádce (3) a vypíše se
chybová zpráva (4).Také při čtení může nastat chyba (výjimka IOException), např. pokud soubor není určen
pro čtení nebo nastane chyba zařízení. Tento stav
zachycuje blok catch na řádce (5) a vypíše se chybová zpráva (6). Pokud vše proběhne bez problémů, pokračuje program za záchytnými bloky (metoda
skončí).
|
|
Všimněte si, že díky výjimkám je důsledně oddělen "užitečný kód" od kódu,
který pouze ošetřuje chyby. Výjimka jedné třídy může být navíc vyvolána
v hlídaném bloku na několika místech a k ošetření postačuje jeden blok catch. Ve srovnání s klasickým způsobem testování chyb (příkazem if) je
zachycování chyb pomocí výjimek mnohem přehlednější.
13.1. Třídy výjimek
Rodičovskou třídou všech výjimek je Throwable (z balíku java.lang), která má standardně dva přímé potomky:
- Error - reprezentuje fatální chyby, které by neměly být
zachycovány, neboť se z nich nelze zotavit (chyba JVM apod.).
- Exception - slouží jako rodičovská třída pro všechny ostatní
výjimky.
Část stromu výjimek třídy Exception vypadá takto:  Díky objektové hierarchii výjimek je možné zachytit více tříd
výjimek v jednom catch bloku - jedna třída výjimek v sobě zahrnuje
i všechny potomky této třídy. Protože však u záchytných bloků záleží na pořadí, (2) je možné některé potomky zachytit zvlášť
a ostatní nechat bloku zachycující výjimky rodičovské třídy:
Priklad 13.2. |
try {
// ...
} catch(ArithmeticException e) {
// zachycení výjimek třídy ArithmeticException
} catch(Exception e) {
// zachycení všech výjimek třídy Exception , tj.
// InterruptedException , RuntimeException ,
// IndexOutOfBoundsException atd. (kromě ArithmeticException )
}
|
|
Programátor si samozřejmě může vytvořit vlastní třídu výjimek - stačí
vytvořit potomka některé z tříd výjimek z Java Core API (nedoporučuje
se však jako rodičovskou třídu použít přímo Throwable).
13.2. Vyvolání výjimky
Výjimky je možné programově vyvolávat příkazem throw. Jeho syntaxe je:
throw instanceVýjimky ;
Výjimka instanceVýjimky musí být instancí třídy Throwable
nebo jejích potomků.
Priklad 13.3. |
Vyvolání výjimky třídy ArithmeticException se provede pomocí:
throw new ArithmeticException();
|
|
13.3. Deklarace výjimek
Všechny výjimky, které mohou v dané metodě nastat, musí být zachyceny nebo deklarovány (viz 11.2.2.), jinak
překladač ohlásí chybu. (3)
Druhý případ znamená, že
metoda přenechává ošetření deklarovaných výjimek volající metodě - dochází
k tzv. propagaci výjimek (viz 13.4.). Volající metoda
opět musí tyto (resp. všechny) výjimky zachytit nebo deklarovat.Deklarace výjimek se používá zejména v případě, že na dané úrovni (např. přímo
v knihovně) není vhodné provádět ošetření výjimky (není možné rozhodnout, jak
by mělo vypadat ošetření):
Priklad 13.4. |
Konstruktor FileInputStream(), který otevírá soubor zadaného jména
(viz příklad 13.1.) neošetřuje případ, že soubor není nalezen
(nastane výjimka FileNotFoundExeption) a přenechává tuto starost
volající metodě. Ta může nechat uživatele zadat jméno souboru znovu, doplnit
si jméno sama, skončit atd.
|
|
13.4. Propagace výjimek
Výjimka může být uvnitř metody vyvolána dvěma způsoby:
- příkazem throw (viz 13.2.),
- voláním metody, která výjimku deklaruje (viz 13.3.).
Pokud není vyvolaná výjimka zachycena blokem catch (viz str. 13) v metodě, kde byla vyvolána, je předána o úroveň výš
(volající metodě). Takto se výjimka rekurzivně předává dokud nedojde k jejímu
zachycení a v krajním případě je zachycena runtime systémem, který zachycuje
všechny výjimky (standardně vypíše chybovou zprávu a ukončí program). Tento
mechanismus se nazývá propagace výjimek.
Priklad 13.5. |
public class Test {
public static void main(String[] args) {
try {
a(); // (1)
} catch(Exception e) { // (2)
System.out.println("Výjimka zachycena.");
e.printStackTrace(); // (3)
}
}
static void a() throws Exception { // (*)
b(); // (4)
}
static void b() throws Exception { // (**)
throw new Exception(); // (5)
}
}
Program začíná metodou main(), kde je volána metoda a() (1), která
následně volá metodu b() (4), v níž je vyvolána výjimka Exception
(5). Tato výjimka není metodou b() zachycena a je proto propagována do
volající metody a(). Zde rovněž není zachycena a je dále propagována do
metody main(), kde je konečně zachycena (2) a způsobí výpis (3):
Vyjimka zachycena.
java.lang.Exception
at Test.b(A.java:16)
at Test.a(A.java:12)
at Test.main(A.java:4)
Obě metody, v nichž může být výjimka vyvolána, ale není zachycována, ji
deklarují (*), (**).
|
|
13.5. Runtime výjimky
Zvláštní postavení mezi výjimkami mají tzv. runtime výjimky -
instance třídy RuntimeException a jejích potomků. Tyto výjimky nemusí
být v metodách zachycovány ani deklarovány.Runtime výjimky totiž reprezentují chyby, které mohou nastat "kdekoliv"
v programu. Jedná se například o ArrayIndexOutOfBoundsException
(přetečení indexu pole) nebo ArithmeticException (aritmetická chyba -
celočíselné dělení nulou) apod. Ošetřování těchto výjimek by mnohdy znamenalo zbytečnou práci navíc, neboť
například ve for cyklu lze snadno zajistit, aby index pole meze
nepřekročil. Pokud by ArrayIndexOutOfBoundsException nebyla runtime
výjimkou, bylo by ji nutné v každé metodě, která pole používá zachytit
nebo deklarovat.
13.6. Koncový blok (finally)
Dvojici bloků try - catch (viz str. 13) je možné
rozšířit o nepovinný koncový blok finally následovně:
try {
// hlídaný blok
} catch( třídaVýjimek1 jménoProměnné1 ) {
// ošetření výjimky
} finally {
// zde uzavřený kód se provede vždy
}
Koncovým blokem program pokračuje po ukončení hlídaného i záchytného
bloku (tj. ať už výjimka nastane nebo ne). Koncový blok se vykoná dokonce
i v případě, že:
- je v try bloku vyvolána výjimka, kterou žádný catch
blok nezachycuje, např. runtime výjimka (viz 13.5.) -
a to ještě před propagací této výjimky,
- je v hlídaném bloku vyvolán příkaz return (viz 9.13.).
|