Programowanie obiektowe Wykład 04 Maciej Wołoszyn mailto:woloszyn@fatcat.ftj.agh.edu.pl 17 marca 2009 Spis treści 1 Wyjatki i obsługa błędów 2 1.1 Rzucanie wyjątków............................... 2 1.2 Argumenty konstruktorów........................... 3 1.3 Przechwytywanie wyjątków.......................... 4 1.4 Tworzenie własnych wyjątków......................... 5 1.5 Specyfikacja rzucanych wyjątków....................... 7 1.6 Przechwytywanie dowolnego wyjątku..................... 7 1.7 Ponowne rzucanie wyjątków.......................... 8 1.8 Wyjątki typu RuntimeException....................... 8 1.9 Blok finally.................................. 9 1.10 Wyjątki i dziedziczenie............................. 10 1.11 Dopasowywanie wyjątków........................... 11 1.12 Wyjątki sprawdzane............................... 11 1.13 Podsumowanie................................. 12 2 Wykrywanie typów 13 2.1 Obiekt Class.................................. 14 2.2 Refleksje (Odzwierciedlenia).......................... 15 2.3 Podsumowanie................................. 16 Proszę o przesyłanie na ten adres informacji o znalezionych błędach, literówkach oraz propozycji zmian i uzupełnień. Dokument przygotowano za pomocą systemu LATEX. Wszelkie prawa zastrzeżone. 1
Programowanie obiektowe. Wykład 04 2 1 Wyjatki i obsługa błędów Java zaleca (a w przypadku wykorzystania klas bibliotecznych praktycznie wymusza) obsługę błędów za pomocą mechanizmu wyjatków (exceptions) czyli rezygnację z funkcji zwracających kody błędów i ich każdorazowego sprawdzania dotyczy to oczywiście tylko błędów, które mogą się pojawić dopiero na etapie uruchomienia programu, a nie jego kompilacji zalety mechanizmu obsługi wyjątków: możliwość przekazania informacji o błędzie w inne miejsce programu nie zawsze od razu mamy dość informacji, aby coś zaradzić (w przeciwnym wypadku nie potrzebujemy wcale rzucać wyjątku) uporządkowanie obsługi błędów często wystarczy obsługiwać dany błąd w jednym miejscu, dzięki czemu oddzielamy rozwiązanie zasadniczego zagadnienia od obsługi możliwych problemów 1.1 Rzucanie wyjatków instrukcja throw nowy wyjątek (obiekt odpowiedniej klasy! np. bibliotecznej) jest tworzony w zwykły sposób operatorem new wykonanie programu w aktualnym miejscu (metodzie, bloku instrukcji) zostaje przerwane dalszym przebiegiem steruje już procedura obsługi danego wyjątku, tzw. exception handler (o ile jest przygotowana) class Mat { static final double EPSILON = 1e-12; static double div(double x, double y) { return x/y; static double divexc(double x, double y) { if( Math.abs(y) < EPSILON) throw new ArithmeticException(); return x/y; System.out.println("1)"+Mat.div(0,0)); System.out.println("2)"+Mat.divExc(0,0));
Programowanie obiektowe. Wykład 04 3 1)NaN Exception in thread main java.lang.arithmeticexception at Mat.divExc(P01.java:8) at P01.main(P01.java:38) System.out.println("3)"+5/0); Exception in thread main java.lang.arithmeticexception: at P01.main(P01.java:38) nie zawsze rzucanie wyjatku ma sens, np.: / by zero static void pokazrozw(int Wx,int Wy,int W){ if(w!=0){ System.out.print("Wyniki: "); System.out.println(Wx/W+" "+Wy/W); else{ // rozwiazanie na miejscu zamiast wyjatku: System.out.println("W=0!"); 1.2 Argumenty konstruktorów standardowe, biblioteczne klasy wyjątków posiadają dwa rodzaje konstruktorów: domyślne, np. ArithmeticException() przyjmujące opis w postaci String-a, np. ArithmeticException(String s) static double divexc(double x, double y) { if( Math.abs(y) < EPSILON) throw new ArithmeticException("div/0"); return x/y; System.out.println("2)"+Mat.divExc(0,0)); Exception in thread main java.lang.arithmeticexception: div/0 at Mat.divExc(P01.java:8) at P01.main(P01.java:38)
Programowanie obiektowe. Wykład 04 4 jako wyjątki mogą być użyte dowolne obiekty klas dziedziczących po klasie Throwable najczęściej do obsługi każdego typu błędu stosuje się osobną klasę informacja o błędzie jest przechowywana wewnątrz obiektu; czasem jedyną potrzebną informacją jest typ wyjątku i żadne dodatkowe dane nie są w takiej klasie przechowywane 1.3 Przechwytywanie wyjatków rzucając wyjątek zakładamy, że zostanie on gdzieś odebrany i obsłużony aby nie spowodować natychmiastowego opuszczenia bieżącej metody, użycie kodu, który może wyprodukować wyjątek musi być zrealizowane w bloku instrukcje po try następują bloki catch (handlers) po jednym dla każdego typu wyjątku, który chcemy przechwycić kod mogacy generować wyjatki catch( A e ) { obsługa wyjatku typu A catch( B e ) { obsługa wyjatku typu B catch( C e ) { obsługa wyjatku typu C... każdy blok catch zachowuje się podobnie do funkcji, która pobiera dokładnie jeden argument (identyfikator musi być podany nawet jeśli nie jest wykorzystywany i do obsługi wyjątku wystarcza nam znajomość jego typu) bloki catch muszą się znaleźć bezpośrednio za try po wystąpieniu wyjątku uruchamiany jest pierwszy handler z pasującym typem jeśli w bloku try jest kilka metod mogących powodować ten sam wyjątek, to do jego obsługi potrzeba tylko jednego catch int m = 1/0; System.out.println("dalej?"); catch(arithmeticexception e){
Programowanie obiektowe. Wykład 04 5 System.out.println("tak nie mozna..."); System.out.println("teraz dalej!"); tak nie mozna... teraz dalej! 1.4 Tworzenie własnych wyjatków często biblioteczne klasy wyjątków nie opisują dobrze rodzaju błędu, który może mieć miejsce w naszym programie można utworzyć własną klasę reprezentującą wyjątek dziedzicząc po istniejącej już klasie, jeśli to możliwe o podobnym znaczeniu class AaaException extends Exception { class DivByZeroException extends ArithmeticException { class ZeroByZeroException extends ArithmeticException { class Mat { static final double EPSILON = 1e-12; static double divexc(double x, double y) { if( Math.abs(y) < EPSILON) if(math.abs(x) < EPSILON) throw new ZeroByZeroException(); else throw new DivByZeroException(); return x/y; for(int k=0;k<2;k++) double w = Mat.divExc(k,0); catch(divbyzeroexception e) { System.err.println("! x/0!");
Programowanie obiektowe. Wykład 04 6 catch(zerobyzeroexception e) { System.err.println("! 0/0!");! 0/0!! x/0! można oczywiście dodać do klasy dodatkowe informacje, np. o wartościach, które spowodowały wygenerowanie wyjątku class Mat { static final double EPSILON = 1e-12; static double divexc(double x, double y) { if( Math.abs(y) < EPSILON) throw new DivByZeroException(y); return x/y; class DivByZeroException extends ArithmeticException { private double x; public DivByZeroException(double x) { this.x = x; public double x() { return x; double w = Mat.divExc(2,1e-13); catch(divbyzeroexception e) { System.err.print("! x/0!"); System.err.println(" : M="+e.x()); e.printstacktrace();! x/0! : M=1.0E-13 DivByZeroException at Mat.divExc(P03.java:5) at P03.main(P03.java:27)
Programowanie obiektowe. Wykład 04 7 1.5 Specyfikacja rzucanych wyjatków nazwy wszystkich wyjątków, które nie pochodza od klasy RuntimeException muszą być wyszczególnione w deklaracji metody, która może je spowodować służy do tego słowo kluczowe throws void f() throws AaaException { /*... */ metoda nie musi rzucać wyjątku, który deklaruje (przydatne np. dla metod abstrakcyjnych) przykłady wyjątków niesprawdzanych na etapie kompilacji (dziedziczacych po klasie RuntimeException): ArithmeticException IndexOutOfBoundsException NegativeArraySizeException NullPointerException (pełna lista dostępna w dokumentacji) 1.6 Przechwytywanie dowolnego wyjatku ponieważ używane w programach klasy wyjątków dziedziczą po klasie Exception, więc w prosty sposób można spowodować przechwycenie dowolnego z nich: catch(exception e) { System.err.println("JAKIS wyjatek"); jeśli potrzeba przechwytywać dowolny wyjątek, to handler catch(exception e) umieszcza się jako ostatni z bloków catch inaczej zablokujemy możliwość obsługi bardziej szczegółowych typów wyjątków użycie typu Exception powoduje, że nie mamy informacji o konkretnym rodzaju wyjątku; możemy jedynie posłużyć się odziedziczonymi metodami throw new ArithmeticException("BLAD"); catch(exception e) { System.err.println(e.getMessage()); System.err.println(e);
Programowanie obiektowe. Wykład 04 8 Object Throwable Exception Error RuntimeException... DataFormatException ArithmeticException... NullPointerException Rysunek 1: Relacje dziedziczenia pomiędzy niektórymi klasami obsługującymi mechanizm wyjątków. BLAD java.lang.arithmeticexception: BLAD 1.7 Ponowne rzucanie wyjatków może się zdarzyć, że wskazane będzie rzucenie na wyższy poziom przechwyconego już wyjątku (np. przekazanie do dalszej obsługi dowolnego obiektu złapanego przez catch(exception e)) wystarczy w tym celu użyć instrukcji throw podając referencję do przechwyconego wyjątku catch(exception e) { System.err.println("przekazuje dalej.."); throw e; dalsze bloki catch w aktualnym miejscu nie są już sprawdzane wyrzucana jest kompletna informacja o wyjątku nawet jeśli przechwycony został jako Exception, to nie traci się informacji o dokładnym typie 1.8 Wyjatki typu RuntimeException dostępna jest pewna ilość automatycznie rzucanych wyjątków, potomków klasy RuntimeException
Programowanie obiektowe. Wykład 04 9 nie wymagają podania na liście rzucanych przez metodę wyjątków są to tzw. wyjatki niesprawdzane najczęściej się ich nie przechwytuje (sygnalizują błędy w programie, które powinny być usunięte na etapie debuggowania i testowania) przykład: ArrayIndexOutOfBoundsException tak samo jak inne typy wyjątków mogą być rzucane z dowolnego miejsca zbędne są instrukcje w rodzaju: if(x == null) throw new NullPointerException(); ponieważ użycie nie pokazującej na nic referencji x i tak spowoduje wyjątek NullPointerException 1.9 Blok finally pozwala wykonać jakieś instrukcje niezależnie od tego, czy wewnątrz bloku try wyjątek został rzucony, czy też nie gwarantuje wykonanie kodu, do którego mogłoby nie być powrotu jeśli nastąpiłby wyjątek (nawet jeśli brak odpowiedniego dla danego wyjątku bloku catch, to blok finally zostanie wykonany przed przekazaniem wyjątku dalej!) można np. zamknąć otwarte pliki albo połączenia sieciowe pełna postać obsługi wyjątków: instrukcje pod specjalnym nadzorem (mogace rzucać wyjatki typu E1, E2,...) catch(e1 e) { obsługa wyjatku E1 catch(e2 e) { obsługa wyjatku E2 finally { instrukcje wykonywane zawsze throw new ArithmeticException("BLAD"); catch(nullpointerexception e) { System.err.println("NullPointer"); finally { System.err.println("finally...");
Programowanie obiektowe. Wykład 04 10 System.err.println("za pozno..."); finally... Exception in thread "main" java.lang.arithmeticexception: BLAD at P05.main(P05.java:9) należy uważać, aby przypadkiem rzucając wyjątek wewnątrz bloku finally nie spowodować utraty informacji o pierwszym zaistniałym wyjątku, jeśli nie był on dotąd obsłużony wyjątek rzucony wewnątrz finally pojawi się zanim dojdzie do przekazania dalej pierwotnego wyjątku 1.10 Wyjatki i dziedziczenie przy redefiniowaniu odziedziczonej po klasie podstawowej metody można rzucać tylko te wyjątki, na które pozwala deklaracja throws umieszczona w klasie podstawowej zapewnia to prawidłowe działanie m.in. polimorfizmu zastrzeżenie to nie dotyczy wyjątków potomnych od zadeklarowanych w klasie podstawowej ewentualnie można w redefiniowanej wersji metody zrezygnować z rzucania wyjątków class XException extends Exception { class YException extends Exception { class A { void f() throws Exception { void g() throws XException { class B extends A { void f() throws YException { //void g() throws YException { /* ZLE */ ograniczenie wyłącznie do wyjątków zadeklarowanych w klasie bazowej nie dotyczy również konstruktorów konstruktor klasy potomnej musi deklarować wyjątki rzucane przez konstruktor klasy bazowej konstruktor klasy potomnej nie może przechwytywać wyjątków rzucanych przez konstruktor klasy bazowej
Programowanie obiektowe. Wykład 04 11 1.11 Dopasowywanie wyjatków po rzuceniu wyjątku system ich obsługi sprawdza dostępne handler-y w kolejności w jakiej są zapisane; po znalezieniu pasującego do typu wyjątku uznaje się wyjątek za obsłużony i nie jest prowadzone dalsze poszukiwanie handler-ów nie jest wymagane dokładne dopasowanie: obiekt klasy potomnej zostanie obsłużony przez blok catch napisany dla klasy bazowej class AException extends Exception { class BException extends AException { throw new BException(); catch (AException e) { System.out.println("AException"); AException nie można próbować obsługiwać wyjątku klasy potomnej po wcześniejszym obsłużeniu typu podstawowego throw new BException(); catch (AException e) { System.out.println("AException"); // catch (BException e) { // System.out.println("BException"); // /* spowodowaloby blad kompilacji */ 1.12 Wyjatki sprawdzane muszą być obsługiwane albo zadeklarowane jako rzucane (przekazywane dalej) przez metodę jeśli nie chcemy lub nie możemy obsłużyć na miejscu ani zadeklarować jako rzucane przez metodę, to można je opakować wewnątrz wyjątku niesprawdzanego (np. typu RuntimeException)
Programowanie obiektowe. Wykład 04 12 static void f() throws Exception { throw new Exception(); static void g() { f(); catch (Exception e) { throw new RuntimeException(e); public static void main(string[] args) { g(); Exception in thread "main" java.lang.runtimeexception: java.lang.exception at P10.g(P10.java:16) at P10.main(P10.java:9) Caused by: java.lang.exception at P10.f(P10.java:5) at P10.g(P10.java:14) // static void h() { // f(); // /* ZLE - blad kompilacji: P10.java:21: unreported exception java.lang.exception; must be caught or declared to be thrown */ 1.13 Podsumowanie Nie należy przechwytywać wyjątków, z którymi nie wiadomo co zrobić! spójna organizacja obsługi błędów jest szczególnie ważna przy pisaniu komponentów (bibliotek), które mają być używane przez inne programy Wyjątków używa się aby np.:
Programowanie obiektowe. Wykład 04 13 1. naprawiać przyczynę kłopotów, a potem ponownie wywoływać metodę skąd rzucony został wyjątek 2. zaproponować rozwiązanie zastępcze zamiast wywoływać ponownie kłopotliwą metodę 3. zrobić co się da w aktualnym miejscu i przekazać wyjątek (ten sam lub inny) dalej 4. bezpiecznie zakończyć działanie programu 2 Wykrywanie typów wykrywanie dokładnego typu obiektu, jeśli dysponujemy tylko referencją typu bazowego RTTI = Run-Time Type Identification odpowiada za sprawdzanie typu przed rzutowaniem i ew. ClassCastException rzucenie wyjątku class Dane { String opis(){ return "Dane"; void f(object obj) { System.out.println( // obj.opis() /* ZLE (blad komp.) */ // (String)obj /* ZLE (wyjatek) */ ((Dane)obj).opis() ); Dane d = new Dane(); f(d); Dane za pomocą zwracającego wartość logiczną operatora instanceof można sprawdzić, czy obiekt należy do danej klasy void f(object obj) { String s; if(obj instanceof Dane) { s=((dane)obj).opis();
Programowanie obiektowe. Wykład 04 14 else s="cos innego"; System.out.println(s); Dane d = new Dane(); String s = "Napis"; f(d); f(s); Dane Cos innego Uwaga: nie ma to służyć zastępowaniu polimorfizmu, ale ułatwieniu np. wywołania metody specyficznej dla konkretnego typu (a nie dla bazowego)! 2.1 Obiekt Class zawiera informacje o klasie/interfejsie wykorzystywany do tworzenia obiektów danej klasy w wyniku kompilacji umieszczany jest w pliku NazwaKlasy.class i ładowany przez JVM przy pierwszym użyciu danego typu (a nie zaraz przy uruchamianiu programu!) można się do niego odnieść np. poprzez: metodę getclass() zdefiniowaną w klasie Object i zwracającą referencję do odpodwiedniego obiektu Class tzw. literał klasy: NazwaKlasy.class dostępny również dla interfejsów, tablic i typów prostych, np. Dane.class; int.class; dodatkowo dla klas obudowujących typy proste dostępne są pola TYPE takie, że np. int.class Integer.TYPE dostępne są m.in. metody: String getname() nazwa klasy, interfejsu, typu prostego... boolean isinterface() sprawdza, czy klasa jest interfejsem boolean isarray() sprawdza, czy klasa jest tablicą
Programowanie obiektowe. Wykład 04 15 boolean isinstance(object obj) dynamiczny odpowiednik operatora instanceof (wynik=true jeśli obj nie jest null i może być zrzutowane do typu reprezentowanego przez Class) void wypisznazweklasy(object obj) { System.out.println(obj + " jest klasy " + obj.getclass().getname()); Dane d = new Dane(); String s = "Napis"; wypisznazweklasy(d); wypisznazweklasy(s); Dane@11b86e7 jest klasy Dane Napis jest klasy java.lang.string 2.2 Refleksje (Odzwierciedlenia) nie zawsze podczas kompilacji dostępne są informacje o klasach (np. gdy używamy komponentów, klas ładowanych przez sieć itp.) mechanizm refleksji pozwala na uzyskanie informacji o typie obiektu oraz jego polach i metodach analizuje obiekty dopiero podczas wykonywania programu, podczas gdy zwykłe RTTI robi to na etapie kompilacji klasa Class zawiera m.in. metody: Method[] getmethods() zwracająca tablicę publicznych metod Constructor[] getconstructors() zwracająca tablicę publicznych konstruktorów Field[] getfields() zwracająca tablicę publicznych pól potrzebne klasy (Method, Field, Constructor) są dostępne w bibliotece java.lang.reflect import java.lang.reflect.*; class Dane { public String opis() { return "Dane";
Programowanie obiektowe. Wykład 04 16 Method[] m = Dane.class.getMethods(); for(int i = 0; i < m.length; i++) System.out.println(m[i]); public java.lang.string Dane.opis() public native int java.lang.object.hashcode() public final native java.lang.class java.lang.object.getclass() public final native void java.lang.object.wait(long) throws java.lang.interruptedex public final void java.lang.object.wait(long,int) throws java.lang.interruptedexcep public final void java.lang.object.wait() throws java.lang.interruptedexception public boolean java.lang.object.equals(java.lang.object) public final native void java.lang.object.notify() public final native void java.lang.object.notifyall() public java.lang.string java.lang.object.tostring() 2.3 Podsumowanie tam gdzie się tylko da, należy wykorzystywać polimorfizm mechanizm RTTI powinien być używany tylko gdy nie da się użyć polimorfizmu