Obsługa wyjątków 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ć. Generacja sytuacji wyjątkowych 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. Obsługa sytuacji wyjątkowych Blok instrukcji: //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 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. Blok catch musi znajdować się zaraz za blokiem 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(); 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 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 FileInputStream in = new FileInputStream(plik); //Klasa BufferedInputStream umoŝliwia czytanie wiekszych //ilości danych z pliku BufferedInputStream bin = new BufferedInputStream(in); 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(" Plik "+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... "); 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. Definiowanie klasy wyjątków 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(" *** \tnic sie nie stalo to tylko: " + "NaszWyjatek *** \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++) 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: " + e.getclass() + " Z nastepujaca informacja: " + e.getmessage()); pauza(); static int wygeneruj(string s) throws NaszWyjatek 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(" [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. Obsługa wielu wyjątków 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(" "+info+l.kodrejsu+" "); 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 + " na 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 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++) //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(" ***********"); br.printstacktrace(); /*23*/ catch (BrakWolnychMiejsc bwm) bwm.printstacktrace(); catch (Exception e) e.printstacktrace(); finally System.out.println("==================== "); /*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, 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:... 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. Źródło Tutorial "Java - nowy standard programowania w Internecie" Artur Tyloch