Exceptions
Preco exceptions?
Kazdy, kto uz programoval, vie aka je to otrava, zabezpecit program pre
rozne udalosti, ktore nie su zelane, ale vyskytujuce sa a to nie az tak
zriedka. V principe treba osetrit kazdu moznu chybu v programe, pretoze
iba tak dosiahneme jeho robustnost.
V klasickom Cecku, to vacsinou vyzera tak, ze funkcia vrati -1
pri chybe (alebo null). Na nas je potom testovat hodnotu,
ktoru nam vrati a podla toho sa zariadit. Ale sami dobre vieme,
ze je to velmi nepohodlne testovat *kazdu* funkciu a casto
nechavame navratovu hodnotu niekde pomimo. Java obsluhuje chyby pri behu
programu pomocou mechanizmu exceptions.
exceptions - vynimky implementuju mechanizmus zabezpecenia
vzniknutej chyby pri behu programu a preklade.
Exception moze vzniknut z roznych pricin, ci uz z hardwerovych (
zakopnutie o sietovy kabel, havaria disku) alebo softwerovych (
delenie nulou, neexistencia subora).
Velmi vdacnym prikladom na exceptions je indexovanie pola. V pripade,
ze sa snazime pouzit index vacsi ako sme uviedli pri deklaracii,
potom musime ocakavat exception.
Mechanizmus vyvolania exception
Pokial dojde k vyskytu chyby v metode, metoda vytvori objekt typu
exception a preda ho systemu.
V tomto objekte su uchovane informacie o stave programu pred vyvolanim
exception a informacie o type exception. Potom interpreter najde miesto
kde sa ma toto exception obsluzit a zavola tento kod, ktoremu preda ako
parameter vytvoreny objekt typu exception.
Vytvorenie objektu typu exception a jeho predanie systemu,
Americania nazyvaju throwing an exception.
Sposob vyhladania kodu na obsluhu exception
Otazkou je ako najst kod, ktory je schopny obsluzit exception
daneho typu. Vsetko je postavene na organizacii tried a ich metod.
Ako priklad si mozeme uviest citanie zo subora.
Predpokladajme, ze vo vasom objektovom navrhu mame tri metody
metoda1, metoda2, metoda3. Samotne citanie je
implementovane v metode3. My volame metodu1 a zaujima
nas spracovanie chyby iba v tejto metode!
Klasicke spracovanie chyb vyzera priblizne takto:
metoda1 {
int error;
error = call metoda2;
if (error)
OsetriChybu;
else
Pokracuj;
}
int metoda2 {
int error;
error = call metoda3;
if (error)
return error;
else
Pokracuj;
}
int metoda3 {
int error;
error = call readFile;
if (error)
return error;
else
Pokracuj;
}
Ako vidime, v pripade, ze nastane chyba, posuvame ju v zasobniku od
metody3 az k metode1, cez metodu2.
Jazyk Java nam umoznuje nasledovny zapis.
metoda1 {
try {
call metoda2;
} catch (ErroReadException) {
OsetriChybu;
}
}
metoda2 throws ErrorReadException {
call metoda3;
}
metoda3 throws ErrorReadException {
call readFile;
}
V pripade, ze volame metodu1, a nastane chyba pri citani v
metode3, ta vyvola (throws) exception
ErrorReadException, ktore potom zachyti metoda1 tym
sposobom, ze system prehladava zasobnik a nakoniec najde kod, ktory dokaze
spracovat vzniknute exception v metode1 na zaklade
catch (ErrorReadException).
Teraz je nam mozno jasne, ze kazde exception, ktore moze nastat musi
byt spracovane na 'nejakej' urovni, aby system mohol najst obsluhu tohoto
exception.
Skupiny exceptions
Casto spadaju exceptions do skupin. Napriklad v pripade poli moze
nastat niekolko exceptions, avsak jednotlive metody mozu obsluhovat
niektore exceptions detailnejsie. Majme nasledujucu skupinu exceptions:
Exception // zakladne exception, od ktoreho sa odvadzaju dalsie
+
|
+
ArrayException -- InvalidIndexException // pri adresovani do pola
+- NoSuchElementException // pri vyhladavani v poli
+- ElementTypeException // pri vkladani do pola
Ma to vyznam ak chceme v niektorej metode obsluzit vsetky exception,
ktore sa mozu vyskytnut pri praci s polom. Potom pomocou
catch (ArrayException e) {
... // osetrenie exception
}
osetrime vsetky chyby pri praci s polom.
try - catch - finally, throw, throws
Exceptions sa implementuju v tychto blokoch
try,
catch,
finally.
Niekedy vsak nie je vyhodne zachytavat mozne exceptions, ale je lepsie
sa ich vzdat a predat ich inej metode (pomocou zasobnika). Toto mozeme
dosiahnut pomocou throws.
Okrem toho existuje aj podobny prikaz
throw, ktory vytvara/
vyvolava exception.
try blok
Do tohto bloku zahrnieme vsetok kod, ktory moze vyvolat exception,
cize kde moze vzniknut chyba (napr. pri otvarani subora).
V praxi to vyzera takto:
try {
OtvorSubor(NazovSubora);
}
...
Pri rozhodovani, ci dane volanie metody by sme mali/nemali zahrnut
do bloku try, by nam malo pomoct to, ci volana metoda
vyvolava (throws) exception. Druhou vecou je, ci tuto metodu
budeme volat my, alebo bude volana inou metodou, ktoru budeme
pouzivat namiesto nej. V pripade, ze ju budeme volat priamo, mali by
ju uviest v bloku try a zachytit potrebne exception.
Vacsinu exception musime obsluzit v metode, ktora je na poslednom
stupni volania. Toto je kontrolovane priamo prekladacom a taketo
exceptions volame checked exceptions.
V inom pripade, ak danu metodu metoda1 chceme len vyuzit
v dalsej metode metoda2, ktora ju vola a my budeme
priamo vyuzivat len metodu metoda2, potom staci obsluzit
exceptions len v metode metode2 a v metode metoda1
staci vyvolat (posunut) vzniknute exceptions metode, ktora ju volala.
Citim, ze by sa tu hodil priklad, ktory sme uz sice uviedli,
ale je este aktualnejsi ako pred tym.
metoda1 {
try {
call metoda2;
} catch (ErroReadException) {
OsetriChybu;
}
}
metoda2 throws ErrorReadException {
call metoda3;
}
metoda3 throws ErrorReadException {
call readFile;
}
Ako ste si asi vsimli blok try nikdy nie je uvedeny sam.
Pravidlo je take, ze musi byt za nim aspon jeden blok catch
alebo jeden blok finally. Podme sa rychlo pozriet na
catch.
catch blok
S kazdym try blokom musime spojit aspon jeden catch
blok, aby bolo mozne, v pripade vzniku exception urcit, kto/co bude
obsluhovat vzniknute exception. Tychto catch blokov moze byt
aj viac ako jeden, na jeden blok try.
Ukazme si najprv ten jednoduchsi priklad.
try {
handle = OtvorSubor(NazovSubora);
} catch (ErrorOpenFileException e) {
VykonajObsluhuChyby;
}
V jednom bloku try mozem vsak vykonat aj viac metod,
ktore mozu vyvolat aj viac druhov exceptions. Potom potrebujeme
uviest prave tolko blokov catch, kolko potencialnych
exceptions mozeme ocakavat.
Povedzme si, ze hned ako otvorime subor, chceme z neho citat
udaje. Nas predchadzajuci priklad sa zmeni nasledovne:
try {
handle = OtvorSubor(NazovSubora);
CitajUdaje(handle);
} catch (ErrorOpenFileException e) {
VykonajObsluhuChyby_NemozemOtvoritSubor;
} catch (ErrorReadFileException e) {
VykonajObsluhuChyby_NemozemCitatZoSubora;
}
Argumentom catch moze byt lubovolny objekt
odvodeny od triedy Throwable a popisuje typ
exception.
finally blok
Tento posledny blok je urceny pre 'zaverecne upratovanie' po obsluhe
exception.
System vzdy vola blok finally, nezalezi na tom,
co sa stane v bloku try.
Nas priklad s otvaranim suboru by potom mohol vyzerat takto:
try {
handle = OtvorSubor(NazovSubora);
} catch (ErrorOpenFileException e) {
VykonajObsluhuChyby;
} finally {
if ( handle ) // je dobre to testovat
ZatvorSubor(handle);
}
throw & throws
Ludovo povedane: prikaz throw vytvara nove exception a preda ho
systemu. Prikaz throws ho posuva po zasobniku az ku najblizsiemu
bloku catch. Z prikladu to bude urcite jasnejsie:
public OpenFileId OpenFile(String fname) throws FileNotFoundException {
OpenFileId = new OpenFileId();
OpenFileId = OpenFile(fname);
if ( ! OpenFileId )
throw new FileNotFoundException();
return FileOpenId;
}
Metodu OpenFile(...) mozeme dalej pouzit v inych triedach,
resp. ich metodach. Tam mozeme znovu obist obsluhu exception
posunutim ho dalsej metode podla zasobnika.
Naviazanim na predchadzajuci priklad ...
public OpenFileId OpenTXTFile(String fname) throws FileNotFoundException {
return OpenFile(fname);
}
Na koniec obsluhe exception aj tak neujdete. Volanie metody
OpenTXTFile. V realnom svete Javy to vyzera
priblizne takto:
class PrintFile {
public static void main(String argv[]) {
OpenFileId of;
try {
of = OpenTXTFile(arg[0]);
} catch (FileNotFoundException e) {
System.err.println("Nemozem otvorit subor" + arg[0]);
System.out.println("usage: java PrintFile 'txtfile'");
}
...
}
}// class PrintFile
Vytvorenie noveho exception
Ukazme si na skutocne realnom - skompilovatelnom - priklade, ako
vytvorit nove exception, ako ho obsluzit v konecnej aplikacii.
/*
Definujem si nove exception. Budem ho volat 'IsNullException' a
nastane vtedy, ak chcem vypisat na obrazovku retazec
pomocou triedy PrintString.
*/
/*
kazde exception musi byt odvodene od triedy 'Throweable'
alebo od triedy, ktora je potomkom tejto triedy, ci uz
priamym alebo nepriamym.
*/
import java.lang.Throwable;
// samotna definicia naseho exception
public class IsNullException extends Throwable {
public IsNullException() {
super(); // volam konstruktor z 'Throweable'
}
public IsNullException(String s) {
super(s); // volam konstruktor z 'Throweable'
}
}//class IsNullException
// nebolo to nic zlozite a mnoho exceptions, ktore v Jave najdete
// , vyzera presne takto (boli odvodene rovnakym sposobom)
Ako zakomponovat nove exception do kodu.
/*
trieda 'PrintString' sluzi na vypisovanie retazcov na
obrazovku, na standardny vystup. V pripade, ze sa pokusim
vypisat retazec, ktory neexistuje, cize je 'null', tak
vyvolam IsNullException,
definovane v predchadzajucom priklade.
*/
// importnem si potrebne triedy
import java.lang.String;
import java.io.*;
// realizacia vypisovania na obrazovku
public class PrintString {
String str;
public PrintString() { // konstruktor bez parametrov
str = null;
}
public PrintString(String s) { // konstruktor s parametrami
str = new String(s);
}
// pomocou tejto metody zabezpecim samotne vypisovanie
public void PrintStr(String s) throws IsNullException {
if ( s == null ) // ak retazec neexistuje => throw exception
throw new IsNullException("***IsNullException***");
else
System.out.println(s);
}
// podobne obsluzim aj bezparametricke volanie PrintStr()
public void PrintStr() throws IsNullException {
if ( str == null )
throw new IsNullException("***IsNullException***");
else
System.out.println(str);
}
}//class PrintString
A vyuzitie v konecnej aplikacii - A
/*
Tento priklad ukazuje ako pouzit throws, pri NEobsluhe exception.
Vyuzivame triedy IsNullException a
PrintString.
*/
// importnem si potrebne triedy (uz to omielam stale dookola)
import java.io.*;
// hlavna trieda - Applikacia
public class ExceptionApp4 {
public static void main(String argv[]) throws IsNullException {
PrintString meno = new PrintString("Lena");
PrintString priezvisko = new PrintString();
// ak si to spustite, mali by ste to hned pochopit
meno.PrintStr();
priezvisko.PrintStr();
}//public static void main(String argv[])
}//class ExceptionApp4
V pripade, ze spustime triedu ExceptionApp4 dostaneme vysledok:
C:\WEB-DATA\Java\jazyk\priklady>java ExceptionApp4
Symantec Java! ByteCode Compiler Version 1.02a
Copyright (C) 1996 Symantec Corporation
Lena
IsNullException: ***IsNullException***
C:\WEB-DATA\Java\jazyk\priklady>
// ani som necakal, ze to nakoniec pochopim, ale stalo sa!
// TIP pre tych, ktori sa dostali az sem:
// V tomto priklade som zistil, ze ak mate nejaku triedu
// zadefinovanu ako 'public', potom musi byt uvedena v
// samostatnom subore, ktory ma take iste meno ako dana
// 'public' trieda.
A vyuzitie v konecnej aplikacii - B
/*
Tento priklad ukazuje ako pouzit try-catch, pri obsluhe exception.
Vyuzivame triedy IsNullException a
PrintString.
*/
// (vy uz viete co ...)
import java.io.*;
// hlavna trieda - Aplikacia
public class ExceptionApp4b {
public static void main(String argv[]) throws IsNullException {
PrintString meno = new PrintString("Lena");
PrintString priezvisko = new PrintString();
// ak vznikne chyba => jej vlastna obsluha
try {
meno.PrintStr();
priezvisko.PrintStr();
} catch (IsNullException e) {
System.err.println("Nastalo IsNullException");
}
}//public static void main(String argv[])
}//class ExceptionApp4b
V pripade, ze spustite triedu ExceptionApp4b, dostanete vystup
C:\WEB-DATA\Java\jazyk\priklady>java ExceptionApp4b
Symantec Java! ByteCode Compiler Version 1.02a
Copyright (C) 1996 Symantec Corporation
Lena
Nastalo IsNullException
C:\WEB-DATA\Java\jazyk\priklady>