Programowanie obiektowe - Wyklad 04
Transkrypt
Programowanie obiektowe - Wyklad 04
Programowanie obiektowe Wykład 04 Maciej Wołoszyn mailto:[email protected]∗ 17 marca 2009 Spis treści 1 Wyjatki ˛ i obsługa błedów ˛ 1.1 1.2 1.3 1.4 1.5 1.6 1.7 1.8 1.9 1.10 1.11 1.12 1.13 2 2 Rzucanie wyjatków ˛ . . . . . . . . . . Argumenty konstruktorów . . . . . . Przechwytywanie wyjatków ˛ . . . . . Tworzenie własnych wyjatków ˛ . . . . Specyfikacja rzucanych wyjatków ˛ . . Przechwytywanie dowolnego wyjatku ˛ Ponowne rzucanie wyjatków ˛ . . . . . Wyjatki ˛ typu RuntimeException . . Blok finally . . . . . . . . . . . . . Wyjatki ˛ i dziedziczenie . . . . . . . . Dopasowywanie wyjatków ˛ . . . . . . Wyjatki ˛ sprawdzane . . . . . . . . . . Podsumowanie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2 3 4 5 7 7 8 8 9 10 11 11 12 Wykrywanie typów 13 2.1 2.2 2.3 14 15 16 Obiekt Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Refleksje (Odzwierciedlenia) . . . . . . . . . . . . . . . . . . . . . . . . . . Podsumowanie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ∗ Prosz˛ e o przesyłanie na ten adres informacji o znalezionych bł˛edach, literówkach oraz propozycji zmian i uzupełnień. Dokument przygotowano za pomoca˛ systemu LATEX. Wszelkie prawa zastrzeżone. 1 Programowanie obiektowe. Wykład 04 2 1 Wyjatki ˛ i obsługa błedów ˛ • Java zaleca (a w przypadku wykorzystania klas bibliotecznych praktycznie wymusza) obsług˛e bł˛edów za pomoca˛ mechanizmu wyjatków ˛ (exceptions) – czyli rezygnacj˛e z funkcji zwracajacych ˛ kody bł˛edów i ich każdorazowego sprawdzania • dotyczy to oczywiście tylko bł˛edów, które moga˛ si˛e pojawić dopiero na etapie uruchomienia programu, a nie jego kompilacji • zalety mechanizmu obsługi wyjatków: ˛ – możliwość przekazania informacji o bł˛edzie w inne miejsce programu – nie zawsze od razu mamy dość informacji, aby „coś zaradzić” (w przeciwnym wypadku nie potrzebujemy wcale rzucać wyjatku) ˛ – uporzadkowanie ˛ obsługi bł˛edów – cz˛esto wystarczy obsługiwać dany bład ˛ w jednym miejscu, dzi˛eki czemu oddzielamy rozwiazanie ˛ zasadniczego zagadnienia od obsługi możliwych problemów 1.1 Rzucanie wyjatków ˛ • instrukcja throw • nowy wyjatek ˛ (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 wyjatku, ˛ 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 wyjatków ˛ posiadaja˛ dwa rodzaje konstruktorów: – domyślne, np. ArithmeticException() – przyjmujace ˛ 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: at Mat.divExc(P01.java:8) at P01.main(P01.java:38) div/0 Programowanie obiektowe. Wykład 04 4 • jako wyjatki ˛ moga˛ być użyte dowolne obiekty klas dziedziczacych ˛ po klasie Throwable – najcz˛eściej do obsługi każdego typu bł˛edu stosuje si˛e osobna˛ klas˛e • informacja o bł˛edzie jest przechowywana wewnatrz ˛ obiektu; czasem jedyna˛ potrzebna˛ informacja˛ jest typ wyjatku ˛ i żadne dodatkowe dane nie sa˛ w takiej klasie przechowywane 1.3 Przechwytywanie wyjatków ˛ • rzucajac ˛ wyjatek ˛ zakładamy, że zostanie on gdzieś odebrany i obsłużony • aby nie spowodować natychmiastowego opuszczenia bieżacej ˛ metody, użycie kodu, który może wyprodukować wyjatek ˛ musi być zrealizowane w bloku try { instrukcje } • po try nast˛epuja˛ bloki catch (handlers) – po jednym dla każdego typu wyjatku, ˛ który chcemy przechwycić try { 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˛e podobnie do funkcji, która pobiera dokładnie jeden argument (identyfikator musi być podany nawet jeśli nie jest wykorzystywany i do obsługi wyjatku ˛ wystarcza nam znajomość jego typu) • bloki catch musza˛ si˛e znaleźć bezpośrednio za try • po wystapieniu ˛ wyjatku ˛ uruchamiany jest pierwszy handler z pasujacym ˛ typem • jeśli w bloku try jest kilka metod mogacych ˛ powodować ten sam wyjatek, ˛ to do jego obsługi potrzeba tylko jednego catch try { 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˛esto biblioteczne klasy wyjatków ˛ nie opisuja˛ dobrze rodzaju bł˛edu, który może mieć miejsce w naszym programie • można utworzyć własna˛ klas˛e reprezentujac ˛ a˛ wyjatek ˛ – dziedziczac ˛ po istniejacej ˛ 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++) try { 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 wyjatku ˛ 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; } } try { 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 wyjatków, ˛ które nie pochodza˛ od klasy RuntimeException musza˛ 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ć wyjatku, ˛ który deklaruje (przydatne np. dla metod abstrakcyjnych) • przykłady wyjatków ˛ niesprawdzanych na etapie kompilacji (dziedziczacych ˛ po klasie RuntimeException): – – – – ArithmeticException IndexOutOfBoundsException NegativeArraySizeException NullPointerException (pełna lista dost˛epna w dokumentacji) 1.6 Przechwytywanie dowolnego wyjatku ˛ • ponieważ używane w programach klasy wyjatków ˛ dziedzicza˛ po klasie Exception, wi˛ec w prosty sposób można spowodować przechwycenie dowolnego z nich: catch(Exception e) { System.err.println("JAKIS wyjatek"); } • jeśli potrzeba przechwytywać dowolny wyjatek, ˛ to handler catch(Exception e) umieszcza si˛e jako ostatni z bloków catch – inaczej zablokujemy możliwość obsługi bardziej szczegółowych typów wyjatków ˛ • użycie typu Exception powoduje, że nie mamy informacji o konkretnym rodzaju wyjatku; ˛ możemy jedynie posłużyć si˛e odziedziczonymi metodami try { 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 RuntimeException ... Error DataFormatException ArithmeticException ... NullPointerException Rysunek 1: Relacje dziedziczenia pomi˛edzy niektórymi klasami obsługujacymi ˛ mechanizm wyjatków. ˛ → BLAD java.lang.ArithmeticException: BLAD 1.7 Ponowne rzucanie wyjatków ˛ • może si˛e zdarzyć, że wskazane b˛edzie rzucenie na wyższy poziom przechwyconego już wyjatku ˛ (np. przekazanie do dalszej obsługi dowolnego obiektu „złapanego” przez catch(Exception e)) • wystarczy w tym celu użyć instrukcji throw podajac ˛ referencj˛e do przechwyconego wyjatku ˛ catch(Exception e) { System.err.println("przekazuje dalej.."); throw e; } • dalsze bloki catch w aktualnym miejscu nie sa˛ już sprawdzane • wyrzucana jest kompletna informacja o wyjatku ˛ – nawet jeśli przechwycony został jako Exception, to nie traci si˛e informacji o dokładnym typie 1.8 Wyjatki ˛ typu RuntimeException • dost˛epna jest pewna ilość automatycznie rzucanych wyjatków, ˛ potomków klasy RuntimeException Programowanie obiektowe. Wykład 04 9 • nie wymagaja˛ podania na liście rzucanych przez metod˛e wyjatków ˛ – sa˛ to tzw. wyjatki ˛ niesprawdzane • najcz˛eściej si˛e ich nie przechwytuje (sygnalizuja˛ bł˛edy w programie, które powinny być usuni˛ete na etapie debuggowania i testowania) – przykład: ArrayIndexOutOfBoundsException • tak samo jak inne typy wyjatków ˛ moga˛ być rzucane z dowolnego miejsca • zb˛edne sa˛ instrukcje w rodzaju: if(x == null) throw new NullPointerException(); ponieważ użycie nie pokazujacej ˛ na nic referencji x i tak spowoduje wyjatek ˛ NullPointerException 1.9 Blok finally • pozwala wykonać jakieś instrukcje niezależnie od tego, czy wewnatrz ˛ bloku try wyja˛ tek został rzucony, czy też nie • gwarantuje wykonanie kodu, do którego mogłoby nie być powrotu jeśli nastapiłby ˛ wyja˛ tek (nawet jeśli brak odpowiedniego dla danego wyjatku ˛ bloku catch, to blok finally zostanie wykonany przed przekazaniem wyjatku ˛ dalej!) – można np. zamknać ˛ otwarte pliki albo połaczenia ˛ sieciowe • pełna postać obsługi wyjatków: ˛ try { 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 } try { 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 rzucajac ˛ wyjatek ˛ wewnatrz ˛ bloku finally nie spowodować utraty informacji o pierwszym zaistniałym wyjatku, ˛ jeśli nie był on dotad ˛ obsłużony – wyjatek ˛ rzucony wewnatrz ˛ finally pojawi si˛e zanim dojdzie do przekazania dalej pierwotnego wyjatku ˛ 1.10 Wyjatki ˛ i dziedziczenie • przy redefiniowaniu odziedziczonej po klasie podstawowej metody można rzucać tylko te wyjatki, ˛ na które pozwala deklaracja throws umieszczona w klasie podstawowej – zapewnia to prawidłowe działanie m.in. polimorfizmu • zastrzeżenie to nie dotyczy wyjatków ˛ potomnych od zadeklarowanych w klasie podstawowej • ewentualnie można w redefiniowanej wersji metody zrezygnować z rzucania wyjatkó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łacznie ˛ do wyjatków ˛ zadeklarowanych w klasie bazowej nie dotyczy również konstruktorów • konstruktor klasy potomnej musi deklarować wyjatki ˛ rzucane przez konstruktor klasy bazowej • konstruktor klasy potomnej nie może przechwytywać wyjatków ˛ rzucanych przez konstruktor klasy bazowej Programowanie obiektowe. Wykład 04 11 1.11 Dopasowywanie wyjatków ˛ • po rzuceniu wyjatku ˛ system ich obsługi sprawdza dost˛epne handler-y w kolejności w jakiej sa˛ zapisane; po znalezieniu pasujacego ˛ do typu wyjatku ˛ uznaje si˛e wyjatek ˛ 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 {} try { throw new BException(); } catch (AException e) { System.out.println("AException"); } → AException • nie można próbować obsługiwać wyjatku ˛ klasy potomnej po wcześniejszym obsłużeniu typu podstawowego try { throw new BException(); } catch (AException e) { System.out.println("AException"); } // catch (BException e) { // System.out.println("BException"); // } /* spowodowaloby blad kompilacji */ 1.12 Wyjatki ˛ sprawdzane • musza˛ być obsługiwane albo zadeklarowane jako rzucane (przekazywane dalej) przez metod˛e • jeśli nie chcemy lub nie możemy obsłużyć na miejscu ani zadeklarować jako rzucane przez metod˛e, to można je „opakować” wewnatrz ˛ wyjatku ˛ niesprawdzanego (np. typu RuntimeException) Programowanie obiektowe. Wykład 04 12 static void f() throws Exception { throw new Exception(); } static void g() { try { 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ć wyjatków, ˛ z którymi nie wiadomo co zrobić! • spójna organizacja obsługi bł˛edów jest szczególnie ważna przy pisaniu komponentów (bibliotek), które maja˛ być używane przez inne programy • Wyjatków ˛ używa si˛e aby np.: Programowanie obiektowe. Wykład 04 13 1. naprawiać przyczyn˛e kłopotów, a potem ponownie wywoływać metod˛e skad ˛ rzucony został wyjatek ˛ 2. zaproponować rozwiazanie ˛ zast˛epcze zamiast wywoływać ponownie „kłopotliwa” ˛ metod˛e 3. zrobić „co si˛e da” w aktualnym miejscu i przekazać wyjatek ˛ (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 referencja˛ typu bazowego RTTI = Run-Time Type Identification • odpowiada za sprawdzanie typu przed rzutowaniem i ew. ClassCastException rzucenie wyjatku ˛ 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 pomoca˛ zwracajacego ˛ wartość logiczna˛ 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˛epowaniu 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˛e do niego odnieść np. poprzez: – metod˛e getClass() zdefiniowana˛ w klasie Object i zwracajac ˛ a˛ referencj˛e do odpodwiedniego obiektu Class – tzw. literał klasy: NazwaKlasy.class dost˛epny również dla interfejsów, tablic i typów prostych, np. Dane.class; int.class; dodatkowo dla klas obudowujacych ˛ typy proste dost˛epne sa˛ pola TYPE takie, że np. int.class ⇔ Integer.TYPE • dost˛epne sa˛ m.in. metody: String getName() – nazwa klasy, interfejsu, typu prostego. . . boolean isInterface() – sprawdza, czy klasa jest interfejsem boolean isArray() – sprawdza, czy klasa jest tablica˛ 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˛epne sa˛ 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() – zwracajaca ˛ tablic˛e publicznych metod Constructor[] getConstructors() – zwracajaca ˛ tablic˛e publicznych konstruktorów Field[] getFields() – zwracajaca ˛ tablic˛e publicznych pól • potrzebne klasy (Method, Field, Constructor) sa˛ dost˛epne 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 →public →public →public →public →public →public →public →public →public java.lang.String Dane.opis() native int java.lang.Object.hashCode() final native java.lang.Class java.lang.Object.getClass() final native void java.lang.Object.wait(long) throws java.lang.InterruptedEx final void java.lang.Object.wait(long,int) throws java.lang.InterruptedExcep final void java.lang.Object.wait() throws java.lang.InterruptedException boolean java.lang.Object.equals(java.lang.Object) final native void java.lang.Object.notify() final native void java.lang.Object.notifyAll() java.lang.String java.lang.Object.toString() 2.3 Podsumowanie • tam gdzie si˛e tylko da, należy wykorzystywać polimorfizm • mechanizm RTTI powinien być używany tylko gdy nie da si˛e użyć polimorfizmu