|
Podczas działania programu mogą wystąpić
różne sytuacje specjalne, do których należą m.in.
wystąpienia błędu polegającego na próbie otwarcia pliku,
który nie istnieje. Java posiada zapożyczony z języka Ada
mechanizm informowania o błędach: wyjątki (ang.
exceptions). Mechanizm obsługi wyjątków w Javie umożliwia
zaprogramowanie "wyjścia" z takich sytuacji
krytycznych, dzięki czemu program nie zawiesi się po
wystąpieniu błędu wykonując ciąg operacji obsługujących
wyjątek. Generowanie i obsługę sytuacji wyjątkowych w Javie
zrealizowano przy wykorzystaniu paradygmatu programowania
zorientowanego obiektowo.
Wystąpienie sytuacji wyjątkowej przerywa
"normalny" tok wykonywania programu. W Javie sytuacja
wyjątkowa występuje wtedy, gdy program wykona instrukcję throw.
Wyrażenie throw przekazuje
sterowanie do skojarzonego z nim bloku catch (łap, blok
obsługujący wystąpienie sytuacji wyjątkowej). Jeśli nie ma
bloku catch w bieżącej metodzie, sterowanie
natychmiastowo, bez zwracania wartości, przekazywane jest do
metody, która wywołała bieżącą funkcję. W tej metodzie
szukany jest blok catch. Jeśli blok catch nie
zostanie znaleziony, przekazuje sterowanie do metody, która
wywołała tę metodę. Sterowanie przekazywane jest zatem
zgodnie z łańcuchem wywołań metod, aż do momentu znalezienia
bloku catch odpowiedzialnego za obsługę wyjątku.
Wszystkie wyjątki, jakie mogą wystąpić w
programie muszą być podklasą klasy Throwable. Poniższy
rysunek pokazuje hierarchię dziedziczenia klasy Throwable
i jej najważniejszych podklas.

Rysunek 2-4 Hierarchia dziedziczenia klas wyjątków
Wyjątki typu Error występują wtedy,
gdy wystąpi sytuacja specjalna w maszynie wirtualnej (np. błąd
podczas dynamicznego łączenia). Wyjątki tego typu nie powinny
być obsługiwane w "zwykłych" programach Javy. Jest
także mało prawdopodobne, że typowy program Javy spowoduje
wystąpienie wyjątku tego typu.
W większości programów generowane są i
obsługiwane obiekty, które dziedziczą z klasy Exception.
Wyjątek tego typu oznacza, że w programie wystąpił błąd,
lecz nie jest to poważny błąd systemowy.
Szczególną podklasę klasy Exception
stanowią wyjątki, które występują podczas wykonywania
programu, są to wyjątki typu RunTimeExceptions (i jej
podklas np.: NullPointerException, ClassCastException, IllegalThreadStateException
i ArrayOutOfBoundsException) i występują np.: wtedy, gdy
zostaną wyczerpane zasoby systemowe, nastąpi odwołanie do nie
istniejącego elementu tablicy i inne. Gdy wyjątek taki nie jest
obsłużony, program zostaje zatrzymany, a na ekranie pojawia
się nazwa wyjątku oraz klasa i metoda, w której wystąpił.
Dzięki temu wiemy, w którym miejscu kodu wystąpił błąd i
można go szybko poprawić.
Zobaczmy na przykładzie, jak wygląda
wywołanie wyjątku w programie.
Przykład 2.20 Generacja sytuacji wyjątkowych
public class WywolajWyjatek
{
static public void main(String args[]) throws Exception
{
Liczba liczba = new Liczba();
liczba.dziel(1);
}
}
class Liczba
{
int m_i = 10;
int dziel(float i) throws Exception
{
if (i/2 != 0)
throw new Exception("Liczba nieparzysta!");
if (i == 0)
throw new Exception("Dzielenie przez zero!");
return (int)(m_i/i);
}
}
W metodzie Liczba.dziel() klasy WywolajWyjatek, za pomocą frazy throw new
Exception("..."), generujemy wyjątek poprzez
utworzenie obiektu typu Exception i przerywamy wykonanie
metody. Jak już wspomniano obiekt ten musi być typu będącego
podklasą klasy Throwable. W programach zawsze generujemy
wyjątki typu Exceptions lub dowolnej podklasy Exceptions.
W ten sposób można w Javie wywoływać wyjątki, dla sytuacji,
które uważamy za nieprawidłowe. W powyższym przykładzie
założono, że nieprawidłowa jest sytuacja gdy zmienna 'i'
jest liczba nieparzystą lub jest równa zero. W obu przypadkach
generowany jest wyjątek, choć z innym komentarzem. W definicji
metody dziel() użyto frazy throws Exception, jej użycie
informuje maszynę wirtualną Javy i metodę wołającą, że
metoda może generować wyjątek typu Exception. Użycie
tej frazy jest obowiązkowe, jeśli nasza metoda może generować
wyjątek. Każda metoda, która woła metodę dziel() musi albo mieć blok (catch) obsługujący
wyjątek albo informację throws Exception, że może być
źródłem wyjątku pochodzącego z metody, którą woła w swoim
ciele. Przykładem tego jest metoda main(),
która nie ma obsługi wyjątku a tylko frazę throws
Exception. W naszej aplikacji wygenerowany błąd nie zostaje
nigdzie obsłużony, więc program kończy działanie, a na
ekranie widzimy:

Ilustracja 2-4 Rezultat wykonania aplikacji WywolajWyjatek.
Wiadomo, że wyjątek może wystąpić w
programie właściwie w każdym momencie jego wykonania. Nie jest
wymagane użycie frazy throws NazwaKlasyWyjątku w
nagłówku deklaracji metody dla błędów klasy RunTimeException
lub jej podklas. Umieszczenie frazy throws dla tych
przypadków jest jednak dobrym pomysłem, szczególnie wtedy, gdy
sami generujemy jeden z powyższych wyjątków w swojej metodzie.
Blok instrukcji:
try
{
//blok instrukcji gdzie może wystąpić wyjątek
}
catch (ObiektImplementujacyInterfejsThrowable nazwaZmiennej)
{
//blok instrukcji obsługujących wystąpienia sytuacji wyjątkowej
//jest wykonywany tylko, gdy wystąpi wyjątek typu takiego jak
// typ zmiennej będącej parametrem bloku catch
}
catch (ObiektImplementujacyInterfejsThrowable nazwaZmiennej)
{ . . . }
catch (ObiektImplementujacyInterfejsThrowable nazwaZmiennej)
{ . . . }
finally //opcjonalnie
{
// ten blok instrukcji jest wykonywany przed opuszczeniem
// sterowania, nawet jeśli blok try zawiera instrukcję
// return lub spowodował wystąpienie wyjątku
}
przeznaczony jest do obsługi wystąpienia
sytuacji wyjątkowych. Blok catch jest fragmentem programu
wykonywanym w przypadku wystąpienia wyjątku w bloku try.
Blok catch musi znajdować się zaraz za blokiem try
lub następnym blokiem catch. Użycie wielu bloków catch
pozwala obsłużyć wystąpienie wyjątków różnych typów.
Przykładem użycia bloku catch niech
będzie niewiele zmieniona metoda main() z
klasy WywolajWyjatek:
static public void main(String args[]) throws Exception
{
Liczba liczba = new Liczba();
try
{ liczba.dziel(1); }
catch (Exception e)
{ e.printStackTrace(); }
pauza();
}
Dodano tu obsługę wystąpienia wyjątku typu Exception
w metodzie liczba.dziel(). Wyjątek w bloku catch może zostać
obsłużony na wiele sposobów. W naszym przypadku, obsługa
wystąpienia wyjątku polega na wydrukowaniu na ekranie (w tym
celu użyto standardowej metody printStackTrace() z klasy Exception) ścieżki wywołań do
metody, w której wystąpił wyjątek. Informacje te są może
niezbyt ważne dla użytkownika ale mają ogromne znaczenie dla
programisty w procesie pisania i testowania kodu.

Ilustracja 2-5 Rezultat wykonania aplikacji WywolajWyjatek po zmodyfikowaniu metody main().
Ewentualne wystąpienie wyjątku w metodzie dziel() dzięki zastosowanie bloku catch w metodzie main()
zostanie obsłużone. Gdyby nie to, że także metoda pomocnicza pauza() w metodzie main() może
generować wyjątek, użycie frazy throws Exception byłoby
w metodzie main() nadmiarowe, choć nie byłoby błędem, ponieważ
wyjątek może wystąpić w innym miejscu programu.
Sterowanie opuszcza blok try w przypadku
wystąpienia instrukcji return lub sytuacji wyjątkowej.
Java pozwala jednak zdefiniować blok instrukcji, które będą
wykonane zanim sterowanie opuści metodę niezależnie od tego,
czym jest to spowodowane. Jest to blok finalny (ang. finally
block), nazywany tak od słowa kluczowego finally. W
języku C++ nie ma odpowiednika bloku finalnego z Javy.
Poniżej prezentujemy przykład programu,
który wyświetla na ekranie zawartość pliku: tekst.txt i próbuje zrobić to samo dla nieistniejącego pliku nieistniejacy.txt.
Przykład 2.21 Obsługa wyjątków przy operacji
czytania z pliku
import java.io.*;
class ReadFile
{
public static void main(String[] args) throws Exception
{
//Proba wyswietlenia na ekranie pliku tekst.txt
PokazPlik(new File("tekst.txt"));
//Proba wyswietlenia na ekranie zawartości
// nieistniejacego pliku
PokazPlik(new File("nieistniejacy.txt"));
//Zatrzymanie wyniku dzialania programu na ekranie
pauza("Koniec programu");
}
static void PokazPlik(File plik) throws Exception
{
try
{
FileInputStream in = new FileInputStream(plik);
//Klasa BufferedInputStream umożliwia czytanie wiekszych
//ilości danych z pliku
BufferedInputStream bin = new BufferedInputStream(in);
try
{
byte bTablica[] = new byte[10];
int nPrzeczytanychBajtow;
System.out.println("Dane z pliku "+plik.getName());
while(bin.available()>0)
{
//czyanie danych z pliku
nPrzeczytanychBajtow = bin.read(bTablica);
//wyprowadzenie danych na ekran
System.out.write(bTablica);
}
}
//przechwycenie wyjątków podczas czytania z pliku
catch (IOException ioe)
{
System.out.println(ioe.toString());
}
finally
{
//zamkniecie pliku
in.close();
System.out.println("\nPlik "+plik.getName()+" zamkniety");
}
}
// przechwycenie wyjątków podczas otwierania pliku
catch (IOException ioe)
{
System.out.println("Blad przy otwarciu pliku " + plik.getName());
ioe.printStackTrace();
}
finally
{
pauza("Koniec czytania");
}
}
static void pauza(String s) throws Exception
{
System.out.print(s+" Nacisnij Enter.....\n");
System.in.read();
}
}

Ilustracja 2-6 Wynik działania aplikacji ReadFile
Zastosowanie bloku finally pozwala
uniknąć dublowania kodu, który musiałby być napisany
zarówno dla przypadku, gdy wystąpi wyjątek, jak i dla
normalnego toku wykonania programu. Blok finalny jest odpowiednim
miejscem do zwolnienia zasobów zarezerwowanych przez metodę,
ponieważ zasoby te powinny być zwolnione niezależnie od tego,
czy wykonanie programu przebiegło w sposób zaplanowany, czy
też wystąpił wyjątek.
W Javie umożliwiono definiowanie klasy
wyjątków, które będą obsługiwały sytuacje, uznane przez
programistę za wyjątkowe. Zaprezentujemy przykład, w którym
zdefiniowano klasę wyjątków NaszWyjatek,
która jest podklasą klasy Exception.
Przykład 2.22 Definicja własnej klasy wyjątków
class NaszWyjatek extends Exception
{
NaszWyjatek()
{ this(""); }
NaszWyjatek(String s)
{
super("\n***\n\tNic sie nie stalo to tylko: " + "NaszWyjatek\n***\n\t"+s);
}
}
Zdefiniujmy teraz klasę Wyjatek
definiującą wyjątki: NaszWyjatek,
operację dzielenia przez zero, odwołania do nieistniejącego
obiektu, odwołania do elementu tablicy poza jej zakresem.
public class Wyjatek
{
static void pauza() throws Exception
{ ... }
public static void main(String[] args) throws Exception
{
String wyjatki[] ={"dzielenie","null","test","tablica"};
for (int i = 0; i < 4; i++)
{
try
{
wygeneruj(wyjatki[i]);
System.out.println("Wyjatek przy operacji typu:\"" + wyjatki[i] + "\" nie zostal wygenerowany");
}
catch (Exception e)
{
System.out.println("Przy operacji typu \"" + wyjatki[i] + "\" wystapil wyjatek: \n" + e.getClass()
+ "\n Z nastepujaca informacja: " + e.getMessage());
}
}
pauza();
}
static int wygeneruj(String s) throws NaszWyjatek
{
try
{
if (s.equals("dzielenie"))
{
int i = 0;
return i/i;
}
if (s.equals("null"))
{
s = null;
return s.length();
}
if (s.equals("test"))
{ throw new NaszWyjatek("Test sie powiodl"); }
if (s.equals("tablica"))
{
int t[] =new int[5] ;
return t[6];
}
return 0;
}
finally
{
System.out.println("\n[wygeneruj(\"" + s +"\") zakonczone]");
}
}
}
Jak widać na ilustracji 2-7 aplikacja w Javie
po wystąpieniu wyjątków tego rodzaju nie zawiesza się ale
może je obsłużyć. W przykładzie obsługa sytuacji
wyjątkowej sprowadza się do wydrukowania na ekranie informacji
o wystąpieniu wyjątku i dodatkowego tekstu komentarza.

Ilustracja 2-7 Wynik wykonania aplikacji Wyjatek.
Pojedyncza metoda może spowodować
wystąpienie wyjątków różnego rodzaju. Aby przedstawić
sposób obsługi wielu wyjątków napiszmy szkielet aplikacji
przeznaczonej do rezerwacji miejsc na loty do różnych miast.
Przykład 2.23 Obsługa wyjątków różnego typu
Na początku zdefiniujmy klasę Lot opisującą
pojedynczy rejs samolotu.
class Lot
{
int m_nIloscMiejsc, m_nWolneMiejsca, m_nZarezerwowane;
// Tablica miejsca[] zawiera informacje o pasażerach,
// którzy zarezerwowali poszczególne miejsca w samolocie.
Pasazer miejsca[];
String KodRejsu;
//... definicje innych pol danych
Lot(int iloscMiejsc, String kod) // Konstruktor klasy Lot
{
m_nIloscMiejsc = iloscMiejsc;
// utworzenie tablicy wskaźników na obiekty typu Pasazer
miejsca = new Pasazer[iloscMiejsc];
m_nWolneMiejsca = iloscMiejsc;
// na początku nie ma żadnego zarezerwowanego miejsca
m_nZarezerwowane = 0;
KodRejsu = kod;
}
// Metoda SprawdzWolneMiejsca() sprawdza czy są jeszcze
// wolne miejsca na bieżący lot a w razie ich braku
// powoduje wystąpienie wyjątku typu BrakWolnychMiejsc
int SprawdzWolneMiejsca() throws BrakWolnychMiejsc
{
if (m_nWolneMiejsca == 0)
{
throw new BrakWolnychMiejsc(this);
}
return m_nWolneMiejsca;
}
//... definicje innych metod klasy Lot
}
Zdefiniujmy też klasę wyjątku BrakWolnychMiejsc, występującą wtedy, gdy nie ma już wolnych miejsc
na dany lot:
class BrakWolnychMiejsc extends Exception
{
BrakWolnychMiejsc(Lot l, String info)
{
// Wywołanie konstruktora nadklasy: Exception(String)
super("\n"+info+l.KodRejsu+"\n");
}
BrakWolnychMiejsc(Lot l)
{
// Wywołanie pierwszego konstruktora tej klasy
this(l,"Brak wolnych miejsc na lot :");
}
}
Zdefiniujmy także klasę BrakRezerwacji jako podklasę klasy BrakWolnychMiejsc. Widać, że definiowane przez nas klasy wyjątku mogą
w dowolny sposób obsługiwać wystąpienie wyjątku. (W naszym
przykładzie klasy wyjątków ograniczają się do przygotowania
odpowiednich komunikatów dla użytkownika.)
class BrakRezerwacji extends BrakWolnychMiejsc
{
BrakRezerwacji(Lot l, Pasazer p)
{
// Wywołanie konstruktora nadklasy: BrakWolnychMiejsc(Lot, String)
super(l,"Nie bylo rezerwacji na nazwisko " + p.Nazwisko + "\nna lot ");
}
}
Klasa Pasazer
opisuje pasażera i takie jego właściwości jak: imię i
nazwisko (pole Nazwisko), rezerwację (pole Rezerwacja
czyli referencja na obiekt typu Lot -
opisujący lot na jaki pasażer zarezerwował miejsce).
class Pasazer
{
String Nazwisko;
// dzięki deklaracji private informacja o rezerwacji dostepna jest
// tylko poprzez metody tej klasy
private Lot Rezerwacja;
//... definicje innych pol danych
Pasazer(String Nazwisko, Lot lot) throws BrakWolnychMiejsc
{
//Sprawdzamy czy na lot sa wolne miejsca
if ((lot != null) && (lot.m_nWolneMiejsca == 0))
throw new BrakWolnychMiejsc(lot);
this.Nazwisko = Nazwisko;
Rezerwacja = lot;
System.out.println(this.Nazwisko+
" rezerwacja na lot"+lot.KodRejsu);
}
// metoda ta sprawdza czy pasażer ma rezerwację na lot l
// gdy takiej rezerwacji nie posiada generowany jest
// wyjątek BrakRezerwacji
boolean SprawdzRezerwacje(Lot l) throws BrakRezerwacji
{
if (Rezerwacja != l)
throw new BrakRezerwacji(l,this);
return true;
}
//... definicje innych metod
}
W celu sprawdzenia działania wszystkich
powyżej zadeklarowanych klas stworzono klasę Rezerwacja, w której w ciele metody main()
wywołana jest metoda test().W metodzie test() w
bloku try tworzymy kolejne obiekty typu Pasazer (patrz linia /*14*/). Ponieważ dla obiektu lot[0] reprezentującego lot do Londynu liczba wolnych miejsc
ustawiona została na zero (/*10*/), podczas próby utworzenia
tego obiektu wygenerowany zostanie wyjątek BrakWolnychMiejsc.
public class Rezerwacja
{
public static void main(String args[]) throws Exception
{
test();
}
static void test() throws Exception
{
int iloscLotow = 3;
// deklaracja i inicjalizacja tablicy pas[]
// zawierającej informacje o nazwisku i imieniu
// pasażera, dane te zostaną użyte przy inicjalizacji
// tablicy pasażer[]
String pas[] = {"Kowalski Artur","Nowak Olga","Egg Jan"};
// deklaracja i utowrzenie tablicy pasazer[] referencji do
// obiektów typu Pasażer (bez inicjalizacji)
Pasazer pasazer[] = new Pasazer[pas.length];
Lot lot[] = new Lot[iloscLotow] ;
// inicjalizacja tablicy lot[]
lot[0] = new Lot(250,"Londyn 0566-45g BA");
lot[1] = new Lot(150,"Los Angelse 0235-45g A&A");
lot[2] = new Lot(250,"New York 0345-65 Lot");
// ustawienie ilości wolnych miejsc na 0 dla lotu do Londynu
// robimy to aby wymusić wystąpienie wyjątku
// BrakWolnychMiejsc dla próby rezerwacji miejsc na ten lot
/*10*/ lot[0].m_nWolneMiejsca = 0;
for (int i=0;i<iloscLotow;i++)
try
{
//Próba rezerwacji dla pasażera pas[i] na lot lot[i]
/*14*/ pasazer[i] = new Pasazer(pas[i],lot[i]);
// Tu sprawdzamy, czy pasazer[1] ma rezerwację
// na lot lot[0], a ponieważ nie ma tej rezerwacji
// wygenerowany zostanie wyjątek BrakRezerwacji
if (i==2)
pasazer[1].SprawdzRezerwacje(lot[0]);
}
/*18*/ catch (BrakRezerwacji br)
{
System.out.println("\n***********");
br.printStackTrace();
}
/*23*/ catch (BrakWolnychMiejsc bwm)
{
bwm.printStackTrace();
}
catch (Exception e)
{
e.printStackTrace();
}
finally
{
System.out.println("====================\n");
}
/*35*/ pauza();
}
static void pauza() throws Exception
{ /* ... Zdefiniowana już wcześniej w tej pracy */ }
}
Ewentualne wystąpienie wyjątków typu BrakWolnychMiejsc i BrakRezerwacji jest obsłużone w metodzie test(), nie
ma potrzeby informowania o ich wystąpieniu metod wołających
metodę test() (fraza throws). W nagłówku metody test()
mamy jednak frazę: throws,
informuje ona metody wołające ją,
że w metodzie tej może wystąpić wyjątek typu Exception
pochodzący z metody pauza()/*35*/.
Po wystąpieniu wyjątku w bloku try,
Java porównuje wyjątek, który wystąpił z parametrami
poszczególnych bloków catch. Przypuśćmy, że
wystąpił wyjątek typu BrakWolnychMiejsc, pierwszy blok catch /*18*/ nie obsługuje tego
wyjątku, więc sterowanie przekazywane jest do drugiego bloku
/*23*/, który obsłuży wystąpienie tego wyjątku. W ten
sposób możemy obsłużyć wystąpienie wyjątków różnego
rodzaju.

Ilustracja 2-8 Wynik wykonania aplikacji Rezerwacja.
Należy jednak pamiętać, że wyjątek może
być obsłużony nie tylko wtedy, gdy parametrem bloku catch
będzie zmienna typu takiego, jak typ wyjątku, który
wystąpił. Wyjątek będzie obsłużony także w przypadku, gdy
parametrem bloku catch będzie zmienna typu, z którego
dziedziczy typ wyjątku. Dlatego, gdyby dla naszego przykładu
kolejność obsługi wyjątków była następująca:
try
{ ... }
catch (Exception e)
{ ... }
catch (BrakRezerwacji br)
{ ... }
catch (BrakWolnychMiejsc bwm)
{ ... }
wyjątki typu BrakRezerwacji i BrakWolnychMiejsc nigdy nie zostałyby obsłużone w bloku catch
do tego przeznaczonym ale zawsze w bloku catch (Exception e). Większość kompilatorów Javy dla takiego
przypadku generuje błąd kompilacji, np. Microsoft Visual J++ po
kompilacji wyświetli komunikat:
error J0102:
Handler for 'BrakRezerwacji' hidden by earlier handler for 'Exception'
error J0102:
Handler for 'BrakWolnychMiejsc' hidden by earlier handler for 'Exception'
Należy więc pamiętać aby bardziej ogólne
bloki catch obsługi wyjątków umieszczać dalej.
spis treści
|