Rozdział 11 - Wyjątki - Pomijanie łapania wyjątków

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):

fragment klasy Osoba.java
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:

fragment metody main z klasy Osoba.java
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.

Tak naprawdę to nie metoda stworzPelnoletniaOsobe, lecz konstruktor klasy Osoba, rzuca wyjątek NieprawidlowaWartoscException, ale z racji tego, że metoda stworzPelnoletniaOsobe tego wyjątku nie obsługuje, to wywołując tą metodę może zajść sytuacja, która spowoduje pojawienie się takiego wyjątku.

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:

Nazwa pliku: MainUzywaThrows.java
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:

MainUzywaThrows.java:7: error: unreported exception PewienWyjatekException; must be caught or declared to be thrown pewnaMetoda(); ^ 1 error

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:

Exception in thread "main" PewienWyjatekException at MainUzywaThrows.pewnaMetoda(MainUzywaThrows.java:12) at MainUzywaThrows.main(MainUzywaThrows.java:7)

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.

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Nie musisz podawać swojego imienia, e-mailu, ani strony www, aby opublikować komentarz. Komentarze muszą zostać zatwierdzone, aby były widoczne na stronie.