Spis treści
Możemy zadać teraz pytanie: a co, jeżeli nie chcemy obsługiwać wyjątków (bądź obsłużyć tylko część z nich)? Czy zawsze musimy obsłużyć wszystkie możliwe wyjątki rzucane przez daną metodę (zadeklarowane za pomocą throws)?
Nie, ale w takim przypadku musimy w metodzie, która nie chce obsłużyć jakiegoś wyjątku (bądź wszystkich), zawrzeć informację, że może ona rzucić dany wyjątek za pomocą poznanego słowa kluczowego throws. Spójrzmy na przykład (korzystający z klasy Osoba):
public static Osoba stworzPelnoletniaOsobe(String pierwszeImie, String nazwisko) throws NieprawidlowaWartoscException { // 1 Osoba result = null; try { result = new Osoba(pierwszeImie, nazwisko, 18); } catch (NieprawidlowyWiekException e) { // 2 // nic nie robimy, bo podajemy poprawny wiek - nie zakladamy bledu } return result; }
Zdefiniowaliśmy powyżej nową metodą – stworzPelnoletniaOsobe. Ma ona na celu stworzenie obiektu klasy Osoba, która ma wiek równy 18. Nie chcemy w tej metodzie obsługiwać przypadku, gdy ktoś poda nieprawidłowe imię bądź nazwisko – w związku z tym, by kompilator nie protestował, że nie obsłużyliśmy wyjątku NieprawidlowaWartoscException, dodaliśmy klauzulę throws do metody stworzPelnoletniaOsobe (1) – oznacza to, że ten, kto wywoła metodę stworzPelnoletniOsobea, będzie musiał:
- obsłużyć wyjątek NieprawidlowaWartoscException lub
- także zdefiniować, że rzuca wyjątek NieprawidlowaWartoscException, jezeli nie będzie chciał go obsłużyć.
Możemy powiedzieć, że metoda stworzPelnoletniaOsoba oddelegowuje obsługę wyjątku NieprawidlowaWartoscException do metody, która będzie z niej korzystała.
Zauważmy, że sekcja catch w powyższej metodzie nie zawiera żadnych instrukcji (2) – zakładamy, że wyjątek NieprawidlowyWiekException nie zostanie rzucony, ponieważ podajemy poprawny wiek. Nie zmienia to faktu, że musimy obsługę tego wyjątku zawrzeć w tej metodzie, ponieważ kompilator języka Java nie jest w stanie wywnioskować, że w tym przypadku wyjątek na pewno nie będzie rzucony.
W metodzie main dodajemy poniższy fragment kodu, w którym korzystamy z metody stworzPelnoletniaOsobe:
try { Osoba osobaPelnoletnia = stworzPelnoletniaOsobe("Jan", null); } catch (NieprawidlowaWartoscException e) { System.out.println("Nieprawidlowa wartosc: " + e.getMessage()); }
Ponieważ metoda stworzPelnoletniaOsobe deklaruje za pomocą throws, że może rzucić wyjątek NieprawidlowaWartoscException, stosujemy try..catch w powyższym fragmencie kodu. Nie obsługujemy wyjątku NieprawidlowyWiekException, ponieważ obsługą tego wyjątku zajmuje się metoda stworzPelnoletniaOsobe.
Silent catch¶
Obsługa wyjątków często nie jest łatwa – trzeba się zastanowić, jak program powinien zachować się, gdy wystąpi pewien wyjątek:
- Czy program powinien kontynuować działanie?
- Czy użytkownik powinien zostać powiadomiony o błędzie?
- Czy program powinien odczekać i spróbować ponownie wykonać kod, który spowodował wyjątek?
Obsługa wyjątków zawsze wiąże się z wymogiem napisania dodatkowego kodu, co nierzadko zajmuje sporo czasu.
Czasem możemy mieć chęć po prostu złapać wyjątki w sekcji catch, ale nie pisać żadnego kodu, który by je obsługiwał:
try { Osoba o = new Osoba("Adrian", "Sochacki", 30); } catch (Exception e) { // zlap wszystkie wyjatki i sie nie przejmuj! }
Tak zapisany kod zaoszczędza nam czas kosztem problemów, które ze sobą niesie. Powyższy sposób zapisu kodu to tzw. silent catch. Ja spotkałem się jeszcze z polskim określeniem "połykanie wyjątków".
Przez to, że nie obsługujemy wyjątków, nie mamy sposobu aby się dowiedzieć, że coś poszło w naszym programie nie tak, bo wszelkie błędy wynikłe z wykonania danej metody są ignorowane.
Może to powodować niestabilne działanie programu i bardzo trudne do wychwycenia błędy, których analiza, znalezienie, i naprawienie, zajmą dużo więcej czasu, niż napisanie dobrego kodu obsługi wyjątku na samym początku tworzenia programu.
Czasem napotkasz się na sytuacje, w których po prostu nie będziesz miał potrzeby obsługi wyjątków lub będziesz pewien, że nie będą rzucone – wtedy silent catch może być dobrym rozwiązaniem. Miej jednak na uwadze, że ignorowanie błędów ze względu na trudność ich obsługi to nie wymówka, aby tego nie robić.
Pomijanie try..catch w metodzie main¶
Może się zdarzyć sytuacja, w której będziemy w metodzie main korzystać z metody, która może rzucić wyjątek rodzaju Checked i z jakiegoś powodu nie będziemy chcieli tego wyjątku obsługiwać. W takim przypadku kompilator zgłosi błąd widząc, że wyjątek rodzaju Checked nie jest obsługiwany:
class PewienWyjatekException extends Exception { } public class MainUzywaThrows { public static void main(String[] args) { pewnaMetoda(); // 1 } public static void pewnaMetoda() throws PewienWyjatekException { // pewne instrukcje ktore powoduja rzucenie wyjatku throw new PewienWyjatekException(); } }
W metodzie main wywołujemy metodę pewnaMetoda (1), która rzuca wyjątek PewienWyjatekException. Brak obsługi tego wyjątku powoduje błąd kompilacji tej klasy:
Nie musimy jednak tego wyjątku obsługiwać w metodzie main – jak wiemy z jednego z poprzednich rozdziałów, jeżeli metoda nie chce obsłużyć wyjątku, to musi zadeklarować potencjał jego rzucenia w swojej własnej sygnaturze:
public static void main(String[] args) throws PewienWyjatekException { pewnaMetoda(); }
Tak zapisana metoda w powyższym programie powoduje, że kod kompiluje się bez błędów.
Pytanie: kto w takim razie obsłuży ten wyjątek? Ten, kto wywołuje metodę main w naszym programie! A jest to nikt inny jak Maszyna Wirtualna Java – gdy rozpoczyna ona wykonanie naszego programu, wywołuje metodę main. Jeżeli rzucony zostanie nieobsłużony wyjątek, to "złapie" go Maszyna Wirtualna Java i wyświetli komunikat na standardowym wyjściu.
Wynik działania tego programu:
Wyjątek zostaje rzucony w metodzie pewnaMetoda. Jako, że metoda, która ją wywołała, czyli main, nie obsługuje tego wyjątku (nie korzysta z try..catch), a zamiast tego sama deklaruje za pomocą throws, że taki wyjątek może być rzucony, wędruje on dalej. Dalej jest już tylko Maszyna Wirtualna Java, która wywołała naszą metodę main. Wyjątek zostaje obsłużony przez Maszynę Wirtualną Java w ten sposób, że po prostu jego treść i stack trace zostają wypisane na standardowe wyjście.