spotkanie nr 6
Obsługa błędów
Obsługa błędów
● Mechanizm obsługi błędów jest wbudowany w język.
● Nietypowe sytuacje (błędy na które nie można w danym kontekście zareagować) są reprezentowane przez specjalne obiekty – wyjątki.
● Aby ułatwić przekazywanie wyjątków pojawiających się w ciągu
zagnieżdżonych wywołań istnieje możliwość przerwania normalnego toku wykonania programu i zgłoszenia wyjątku przy pomocy instrukcji
throw.
● Wyjątki, które mogą być zgłoszone na zewnątrz muszą (wyjątkowe wyjątki będą omówione później) być wymienione w klauzuli throws
każdej metody.
Przykład
class Wyjątki {
public static void main(String[] args) throws Exception { (new Wyjątki()).głęboko();
}
void głęboko() throws Exception { głębiej();
}
void głębiej() throws Exception { throw new Exception();
//kompilator wykryje miejsca, do których //sterowanie nigdy nie dotrze
Nieobsłużone wyjątki
Nieobsłużone wyjątki wydostając się poza main powodują przerwanie wykonania programu oraz wypisanie komunikatu o błędzie:
Exception in thread "main" java.lang.Exception at niezbędnik6.Wyjątki.main(Wyjątki.java:34)
Szczegółowość informacji o umiejscowieniu błędu w kodzie zależy od ustawień kompilatora.
Obsługa błędów
● Obsługa zgłaszanych wyjątków jest możliwa dzięki blokom try i catch.
● Blokiem try otaczamy instrukcje (np. wywołania metod) mogące zgłosić wyjątek.
● W C trzeba było obsługiwać każdą instrukcję po kolei
● Klauzula catch pełni rolę metody wykonywanej aby obsłużyć wyjątek, jeżeli może on być użyty jako jej argument. Jeżeli typ wyjątku nie pasuje, nie jest obsługiwany w tym bloku, a jego zgłaszanie jest kontynuowane.
●
try { //...
} catch (Typ wyj) {
Przykład
class Wyjątki {
public static void main(String[] args) { //throws Exception { (new Wyjątki()).głęboko();
}
void głęboko() { //throws Exception { try {
głębiej(); //obsługujemy cały blok
głębiej(); //a nie tylko pojedynczą instrukcję throw new Exception();
} catch (Exception e) {
System.out.println("obsługuję wyjątek");
} }
void głębiej() throws Exception { throw new Exception();
Throwable i jej podklasy
Jedynie obiekty dziedziczące po Throwable mogą być używane jako wyjątki. Podstawowym źródłem informacji o rodzaju błędu jest klasa reprezentująca wyjątek.
● Zalecane jest aby każda klasa reprezentująca wyjątek miała przynajmniej dwa konstruktory: bezargumentowy oraz przyjmujący napis z
dodatkowym opisem błędu.
● Od wersji 1.4 do Throwable dodano mechanizm podczepiania wyjątków, które spowodowały dany wyjątek (często obsługa wyjątku polega na
zgłoszeniu innego) - kolejne dwa konstruktory oraz metoda initCause().
● Metoda initCause()jest przykładem niekonsekwencji, bo opis błędu można ustawić jedynie za pomocą konstruktora.
Throwable i jej podklasy
Throwable ma dwie podklasy Error i Exception.
● Error to poważne błędy niezwiązane z działaniem aplikacji, np.
VirtualMachineError, które nie powinny się zdarzyć. Metody nie mają obowiązku deklarować możliwości ich wystąpienia, a zadeklarowanie nie ma żadnego znaczenia.
● Exception to błędy, na które aplikacja powinna reagować.
● RuntimeException to podklasa Exception reprezentująca typowe błędy powstające podczas działania maszyny wirtualnej, np.
NullPointerException. Te wyjątki są wyróżnione i tak jak w wypadku
Error nie ma obowiązku ich deklarować.
Własne wyjątki
class MójWyjątek extends Exception { MójWyjątek(String s) {
super(s);
}
MójWyjątek() {}
}
Własne wyjątki c.d.
public static void main(String[] args) { try {głęboko();} catch (Exception e) { System.out.println(e.getMessage());
System.out.println(e.getCause().getMessage());
} }
static void głęboko() throws Exception { try {głębiej();} catch (MójWyjątek w) {
System.out.println("obsługuję MójWyjątek");
throw new Exception("W kooprocesorze wykryto wodę",w);
} }
static void głębiej() throws MójWyjątek {
throw new MójWyjątek("Włóż dwa hamburgery napędu cd.");
RuntimeException
class Wyjątki {
public static void main(String[] args) { try {
if (true) throw new RuntimeException();
Wyjątki w = null;
w.głęboko();
} catch (RuntimeException e) {
System.out.println(e.getMessage());
}
Wyjątki w = null;
w.głęboko(); //tego wyjątku main nie deklaruje }
Obsługa błędu uzależniona od rodzaju wyjątku
try { //...
} catch (NadklasaObsługiwanychWyjątków wyj) { if (wyj instance Klasa1) {
//...
} else if (wyj instance Klasa2) { //...
} else if (wyj instance Klasa3) { //...
} ...
}
Kilka catch i klauzula finally
Dla jednego bloku try można podać kilka części catch i w różny sposób obsługiwać wyjątki z różnych klas:
try {...}
catch (Klasa1 wyj) {...}
catch (Klasa2 wyj) {...}
catch (Klasa3 wyj) {...}
...
● Dopasowywana jest pierwsza klauzula catch, do której pasuje wyjątek.
Pozostałe klauzule są ignorowane.
● Kompilator nie pozwoli, by któraś klauzula przesłoniła występujące po niej (np. by Klasa2 dziedziczyła po Klasa1).
Finally
import java.util.*;
class Wyjątki {
Random r = new Random(new Date().getTime());
void losowyWyjątek() throws MójWyjątek {
if (r.nextBoolean()) throw new MójWyjątek();
}
public static void main(String[] args) { try {
new Wyjątki().losowyWyjątek();
} catch (MójWyjątek w) {
System.out.println("złapany");
} finally {
System.out.println("finally");
}
Uwaga na wyjątki w finally
Finally jest potrzebne, bo daje możliwość zwolnienia nadaremnie przydzielonych zasobów niesie jednak ze sobą niebezpieczeństwo zgubienia już zgłoszonego wyjątku:
try {
//przydzielamy zasoby throw new MójWyjątek1();
} finally {
//zwalniamy zasoby
throw new MójWyjątek2();
}
Stos wywołań
Zgłaszany wyjątek zbiera informacje o rozwijanym stosie wywołań.
class Wyjątki {
public static void main(String[] args) throws Exception { głęboko();
}
static void głęboko() throws Exception { głębiej();
}
static void głębiej() throws MójWyjątek { throw new MójWyjątek();
} }
Exception in thread "main" niezbędnik6.Wyjątki at niezbędnik6.Wyjątki.głębiej(Wyjątki.java:114)
Stos wywołań
● Zbierany ślad stosu wywołań jest domyślnie wypisywany na standardowe wyjście błędu, jeżeli nieobsłużony wyjątek wydostanie się poza main().
● Za wypisywanie odpowiada odziedziczona z klasy Throwable metoda
printStackTrace(). Dostępne są również jej dwie przeciążone wersje wypisujące informacje na zadany strumień.
● Jeżeli zdecydujemy się ponownie zgłosić właśnie obsługiwany wyjątek, ślad stosu będzie dalej uaktualniany i będzie nadal prowadził do jego rzeczywistego miejsca powstania.
● Jeżeli chcemy, aby ślad stosu prowadził do miejsca ponownego zgłoszenia wyjątku należy użyć metody fillInStackTrace().
● Ta metoda jest również wywoływana w momencie tworzenia wyjątku.
Ponowne zgłaszanie obsługiwanego wyjątku
static void głęboko() throws MójWyjątek { try {
głębiej();
} catch (MójWyjątek w) { w.fillInStackTrace();
throw w;
//throw (MójWyjątek) w.fillInStackTrace();
//ta metoda jest zazwyczaj dziedziczona, aż z Throwable //czy przesłaniając można zmienić jej typ zwrotny
} }
Exception in thread "main" niezbędnik6.MójWyjątek at niezbędnik6.Wyjątki.głęboko(Wyjątki.java:120)
Wyjątki a tworzenie egzemplarza
● Konstruktor musi definiować wszystkie wyjątki mogące powstać podczas tworzenia egzemplarza obiektu.
● W trakcie inicjalizacji statycznej nie można zgłaszać wyjątków.
Przykład
class MojaKlasa {
static void możeZgłosićWyjątek() throws WyjA {}
{ możeZgłosićWyjątek(); }
//static { możeZgłosićWyjątek(); }
static int statyczna() throws WyjB {return 1;}
int normalna() throws WyjC {return 2;}
int i = normalna();
int j = statyczna();
//static k = statyczna();
MojaKlasa() throws WyjA, WyjB, WyjC, WyjD { throw new WyjD();
Zgłaszanie wyjątków w podklasach
● Obiekty podklasy mogą być używane w zamian obiektów nadklasy.
● Rozszerzane metody nie mogą zgłaszać wyjątków, których dotychczas nie trzeba było obsługiwać.
● Rozszerzane metody mogą ograniczyć zgłaszane wyjątki – niektórych nie zgłaszać, niektóre uogólnić.
● Konstruktory podklasy nie mają możliwości obsłużyć wyjątków powstających podczas tworzenia obiektu nadklasy.
Przykład
class BrakPaliwa extends Exception {}
class BrakBenzyny extends BrakPaliwa {}
class BrakGazu extends BrakPaliwa {}
class Samochód {
void jedź() throws BrakPaliwa {}
}
class SamochódNaBenzynę extends Samochód { void jedź() throws BrakBenzyny {}
}
class SamochódONapędzieHybrydowym extends Samochód {
Przykład c.d.
class BłądContinuum extends Exception {}
class ŁamiePrawaFizyki extends Exception {}
interface WehikułCzasu {
void jedź() throws ŁamiePrawaFizyki;
void przenieśSięWCzasie() throws BłądContinuum;
}
class PerpetuumMobile extends Samochód implements WehikułCzasu {
public void jedź() {} //throws ŁamiePrawaFizyki
Przykład c.d. c.d.
class BrakGotówki extends Exception {}
class NiedostępnyWSprzedaży extends Exception {}
class Samochód {
Samochód() throws BrakGotówki {}
void jedź() throws BrakPaliwa {}
}
class SamochódONapędzieHybrydowym extends Samochód { SamochódONapędzieHybrydowym() throws BrakGotówki,
NiedostępnyWSprzedaży {}
void jedź() throws BrakBenzyny, BrakGazu {}
}
Założenia w naszym kodzie
Często w pisanym przez siebie kodzie robimy założenia i opisujemy je w komentarzach.
int x = 0;
int y = 0;
while (true) { x++;
if (y > 5) break;
}System.out.println("Ta maszyna wirtualna jest do bani!");
Wykonywanie pewnych testów może być bardzo przydatne podczas pracy nad programem, ale gdy już się upewnimy, że wszystko działa jak planowaliśmy wypadałoby je wykomentować zamiast wdrażać taki kod u
Asercje
Od wersji 1.4 Java posiada wsparcie do sprawdzania takich założeń.
class Pracownik { float pensja;
private void dajPodwyżkę(float oIle) {
assert (oIle > 0); //może zgłosić wyjątek AssertionError ...
}
private void obniżPensję(float oIle, String dlaczego) {
assert (oIle > 0) : "Pensję można obniżać tylko o dodatnią wartość";
...
} }
Pracownik p = new Pracownik();
AssertionError
AssertionError jest podklasą Error, czyli można go nie obsługiwać.
Czy ten kod robi to samo?
assert (warunek);
if (warunek) throw new AssertionError();
Włączanie/wyłączanie asercji
Domyślnie asercje nie są włączone (są ignorowane).
java -enableassertions lub java -ea
java -disableassertions lub java -da
Można podać dodatkowe parametry
bez parametrów - dotyczy wszystkich klas
z nazwą pakietu kończącą się „...” - dotyczy całego pakietu i jego podpakietów (same „...” wskazują pakiet domyślny)
z nazwą klasy – dotyczy klasy
Wersja bezparametrowa nie dotyczy klas systemowych
(-enablesystemassertions -esa –disablesystemassertions -dsa)
Można podać kilka przełączników naraz są one przetwarzane od lewej do prawej.
Zaczynamy od wyłączonych asercji dla wszystkich klas i wykonujemy
Jak nie używać asercji!
Po assert nie należy podawać wyrażenia, które ma efekty uboczne.
taki program zachowywałby się inaczej w zależności od tego czy asercje są włączone czy wyłączone
Nie należy obsługiwać wyjątku AssertionError.
Nie używać do kontroli argumentów w publicznych metodach.
metoda powinna działać nawet jak użytkownik źle ją wywołał
działanie metody nie może zależeć od włączanie assercji
Nie używać asercji do kontroli argumentów przekazywanych z linii komend.
Kiedy można używać asercji
Do kontroli argumentów w metodach prywatnych.
tylko nasz kod je wykonuje, więc możemy coś zakładać
Do kontroli założeń, które na pewno powinny być prawdziwe (również w metodach publicznych).