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 throwcatch (łap, blok obsługujący wystąpienie sytuacji wyjątkowej). Jeśli nie ma bloku catchcatch. Jeśli blok catchcatch
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
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,throw new Exception("..."), generujemy wyjątek poprzez utworzenie obiektu typu ExceptionThrowable. W programach zawsze generujemy wyjątki typu ExceptionsExceptions. 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()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()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:
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ątkuRunTimeException lub jej podklas. Umieszczenie frazy throws
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 catchtry. Blok catchtry 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 catchmain() 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()catchprintStackTrace()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.
Ewentualne wystąpienie wyjątku w metodzie dziel() dzięki zastosowanie bloku catch 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
Sterowanie opuszcza blok tryreturnfinally. 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 nieistniejacy.txt.
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();
}
}
Zastosowanie bloku finally
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.
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.
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.
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 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)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,main() wywołana jest metoda testtest() w bloku tryPasazer (patrz linia /*14*/). Ponieważ dla obiektu lot[0]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 BrakWolnychMiejscBrakRezerwacjitest(), nie ma potrzeby informowania o ich wystąpieniu metod wołających metodę testthrows). 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()
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 BrakWolnychMiejsccatch /*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.
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
{ ... } catch (Exception e) { ... } catch (BrakRezerwacji br) { ... } catch (BrakWolnychMiejsc bwm) { ... }
wyjątki typu BrakRezerwacjiBrakWolnychMiejsccatch do tego przeznaczonym ale zawsze w bloku catch (Exception
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