Informacje o nowych artykułach oraz akcjach edukacyjnych prosto na Twojej skrzynce e-mail!

Elementarz Java #8 – Obsługa wyjątków

Wstęp

Wyjątki, błędy i inne niepożądane sytuacje. Dzisiaj w ramach serii #ElementarzJava, artykuł na temat obsługi wyjątków. Jak łapać wyjątki? Czy wszystkie błędy należy obsługiwać? Co oznacza słówko throw i throws? To tylko niektóre z pytań, na które znajdziesz odpowiedź w niniejszym materiale. Mam nadzieję, że wpis będzie dla Ciebie pomocny i jak zwykle dowiesz się czegoś nowego. Zapraszam do lektury!

Kategorie wyjątków w języku Java

W Javie wyróżniamy dwa rodzaje wyjątków: checked exceptions – wyjątki, które muszą być łapane przez programistę oraz unchecked exceptions – wyjątki, które z definicji nie powinny być łapane przez programistę (aplikacja w takiej sytuacji powinna się „wysypać”).

Dodatkowo, wyjątki dzielimy również na te, które rzucane są przez JVM (nie są rzucane programistycznie). Przykładowo, zaliczają się do nich: ArrayIndexOutOfBoundsExceptionExceptionInInitializerError, NullPointerException.

Użycie konstrukcji try/catch

Do “łapania” wyjątków w Javie, używamy konstrukcji try/catch. Jej standardowa wersja wygląda tak.

try {
    // …
} catch(Exception e) {
    // …
} finally { 
    // …
}

W bloku try umieszczamy kod, który w wyniki swojego działania może rzucić wyjątek. Wyjątek ten „łapiemy” w bloku catch, a następnie dowolnie go obsługujemy. W bloku finally, umieszczamy instrukcje, które mają się wykonać niezależnie od tego, czy wyjątek został złapany czy też nie. Jeśli pominiemy blok catch, wtedy blok finally jest wymagany (w przeciwnym przypadku jest on opcjonalny).

try {
    //…
} finally {
    //…
}

Powyższy kod zostanie więc skompilowany poprawnie.

try {
    int result = 5;
} catch(RuntimeException e) {
    //…
} catch(ArithmeticException e) {
    //…
}

Taka konstrukcja, jak ta pokazana wyżej jest niedozwolona. Dlaczego? Mamy tutaj klasyczny błąd typu unreachable code czyli kod nieosiągalny. Jeśli w pierwszej kolejności łapiemy wyjątki typu RuntimeException, a następnie ArithmeticException to już w momencie kompilacji, kompilator zdaje sobie sprawę, że tak naprawdę wszystkie wyjątki ArithmeticException zostaną złapane wcześniej, a kod ten nie będzie wywoływany. Wyjątki ArithmeticException dziedziczą po RuntimeException (patrz, diagram na początku artykułu). 

Czy kod znajdujący się w bloku finally jest „zawsze” wywoływany. Jest to prawdą?

try {
    System.out.println("Hello!");
    throw new Excetpion();
} catch(Exception e) {
    System.exit(0);
} finally {
    System.out.print("Ups...");
}

Ten przykład pokazuje, że nie. Po wywołaniu instrukcji System.exit(0) aplikacja zostanie zamknięta, bez wywołania kodu z bloku finally (jest jeszcze kilka takich „kruczków”, które umożliwiają niewywoływania instrukcji z bloku finally). Warto również dodać, że w momencie wystąpienia wyjątku, dalsze instrukcje z bloku try, nie są uruchamiane.

try {
    int result = 5 / 0;
    System.out.print("Hello");
} catch(ArithmeticException e) {
    System.out.println(" World!");
}

Rezultatem uruchomienia powyższego kodu, będzie wyświetlenie napisu „ World!”. Napis „Hello” nie będzie wypisany.

try {
    int result = 5 / 0;
} catch(NullPointerException e) {
    System.out.print("Ups...");
}

Blok catch nie zawsze łapie wszystkie wyjątki. W tym przypadku aplikacja się wysypie. 

try {
    String name = null;
    name.toString();
} catch(NullPointerException e) {
    System.out.println("Hello");
    throw e;
}

Oczywiście wszystkie instrukcje jakie znajdują się przed linijką powodującą błąd, wykonywane są poprawnie. Tak samo jak instrukcje znajdujące się w bloku catch, które uruchamiane są po złapaniu wyjątku. W bloku tym możemy również „rzucić” inny wyjątek. Co widać na przykładzie powyżej. Kod ten wypisze „Hello” i adnotację na temat błędu „NullPointerException”. Ciekawym przykładem jest łapanie wyjątków typu checked exceptions. Jeśli w bloku try, nie ma instrukcji, która taki błąd mogła by wygenerować, a jest on łapany w bloku catch, to już na etapie kompilacji dostaniemy błąd.

try {
    System.out.println("Hello world!");
} catch(IOException e) {
    System.out.println("Life is brutal!");
}

Ten kod nie zadziała. W bloku try nie deklarujemy metody, która rzucałaby błąd IOException. Kompilator „wie”, że blok catch będzie nieosiągalny (ang. unreachable code).

try {
    System.out.println("A");
    throw new NullPointerException();
} catch (NullPointerException e) {
    System.out.println("B");
    throw new RuntimeException();
} catch (RuntimeException e) {
    System.out.println("C");
} finally {
    System.out.println("D");
    throw new RuntimeException("1");
}

Jaki będzie efekt wywołania powyższych instrukcji? Na ekranie komputera otrzymamy następującą sekwencję: A B D 1. Na początku wypiszemy „A” i wyrzucimy wyjątek, który jest „łapany”, wypisujemy „B” i wyrzucamy kolejny wyjątek, ale tym razem przechodzimy od razu do bloku finally, gdzie wypisujemy „D” i wyrzucamy wyjątek z 1.  Po pierwszym bloku catch przechodzimy od razu do finally. Tam wyrzucamy wyjątek, który nie jest obsługiwany, więc kończymy działanie aplikacji. Gdyby wyjątek ten nie był „rzucany”, został by w jego miejsce „rzucony” wyjątek RuntimeException() z pierwszego bloku catch, ale stało by się to „na końcu” po wypisaniu „D”. Jeśli nie rzucalibyśmy w tych dwóch miejscach, wyjątków, to wykonały by się wszystkie instrukcje z pierwszego bloku catch i przeszlibyśmy do finally, gdzie wykonały by się kolejne instrukcje i aplikacja działała by dalej.

Przeznaczenie klas wyjątków

Każdy wyjątek zawiera swego rodzaju „informację” o błędzie. Jeżeli otrzymujemy wyjątek typu ClassCastException to „wiemy”, że poszło coś nie tak z rzutowaniem klas.

Object obj = new Integer(1);
String obj2 = (String) obj;

Powyższy kod „wyrzuci” wyjątek typu ClassCastException. Obiekt String nie dziedziczy po Integer. W przypadku działań arytmetycznych sytuacja jest analogiczna.

System.out.print(1 / 0);

Przy próbie dzielenia przez zero, otrzymamy ArithmeticExcetpion. We wszystkich innych sytuacjach, mamy analogiczne zachowanie.

Metody wyrzucające wyjątki

Wyjątki mogą zostać również „rzucane” przez metody. Taka konstrukcja wymaga użycia słówka kluczowego throws, które umieszczamy po nazwie metody. Następnie podajemy typ wyjątku jaki będziemy rzucać, przy użyciu instrukcji throw. Popatrzmy na przykład.

public void method throws Exception { 
    throw new Exception();
}

W powyższym kodzie zdefiniowaliśmy “rzucany” typ wyjątku na Exception. Nie oznacza to, że w metodzie, możemy używać tylko tego wyjątku. Dozwolone są wszystkie wyjątki, które „zawężają” Exception (patrz, diagram na początku artykułu). Przykładowo może to być RuntimeException. Możemy umieścić też dowolny wyjątek z grupy unchecked exceptions.

public void method() throws IOException {
    System.out.println("It's ok");
 
    throw new Throwable(); // niedozwolone – wyjątek nie jest łapany
    throw new Exception(); // niedozwolone – wyjątek nie jest łapany
    throw new AWTException(""); // niedozwolone – wyjątek nie jest łapany
 
    throw new RuntimeException();
    throw new IllegalArgumentException();
    throw new java.io.IOException();
    throw new EOFException();
 
    throw new Error();
}

Mamy w interfejsie zdefiniowaną metodę, która „rzuca” wyjątek typu Exception.

interface  Document {
    void getType() throws Exception;
}
 
public class Passport implements Document {
    public void getType() ... {
    }
}

Co możemy wstawić w miejsce „…”?

  1. nic – metoda, nie musi „rzucać” żadnych wyjątków,
  2. wszystkie wyjątki typu unchecked exception,
  3. wszystkie wyjątki „zawężające” Exception,
  4. wyjątek Exception.

Gdybyśmy w miejscu Exception mieli np. IOException to moglibyśmy wstawić wszystkie wyjątki typu unchecked exception ale już np. AWTException nie mógłby być (ten sam poziom w hierarchii).

Jeśli już jesteśmy przy temacie interfejsów w kontekście wyjątków, to nie może również zabraknąć przysłaniania metod.

class Animal {
    public void hasMammals() { }
}
 
class Dog extends Animal {
    public void hasMammals() throws RuntimeException() { }
}

Metoda hasMammals znajdująca się w klasie Dog może „rzucać” dowolny wyjątek typu unchecked excpetions. Gdybyśmy chcieli rzucać w tym miejscu inny wyjątek typu checked exceptions, metoda przysłaniana (z klasy bazowej) musiała by mieć zdefiniowany taki sam wyjątek jaki chcemy „rzucić” lub jego „szerszą” wersję.

class Test {
    public static Exception method() {
        return new Exception();
    }
}

Wyjątki, jak każdy normalny obiekt mogą być zwracane przez metody. Co widać na powyższym przykładzie. Popatrzmy na jeszcze jeden przykład.

class FirstException extends Exception { } 
class SecondException extends FirstException { }
 
public class Main {
    public static void main(String[] args) {
        try {
            test();
        } catch(SecondException ex) {
            //…
        } catch(Exception ex) {
            //…
        }
    }
 
    private static void test() throws SecondException {
        throw new SecondException();
    }
}

Do drugiego bloku catch można wstawić np. RuntimeException ponieważ nie jest łapany wcześniej. Dopuszczalne są wszystkie wyjątki „szersze” od SecondException. W tym przypadku będzie to FirstException oraz Exception. Tutaj jedna uwaga – FirstException jako wyjątek, który dziedziczy po Exception jest wyjątkiem typu Checked Exception. Kompilator wie, że żadna instrukcja z bloku try, takiego wyjątku nie „rzuci”. Drugi blok catch będzie oznaczony jako nieosiągalny, ale nie spowoduje to błędu kompilacji. Jeśli FirstException dziedziczył by po jakimś wyjątku unchecked exceptions np. Error to wtedy żadnych komunikatów nie będzie. Kompilator nie będzie wiedział, że wyjątek taki nie wystąpi. Dodatkowo możemy też wstawić dowolny wyjątek typu unchecked exceptions. Pamiętaj, że zamiana kolejności tzn. w pierwszym bloku wstawienie FirstExcetpion, a w drugim SecondException jest niedopuszczalne – mamy wtedy unreachable code.

Spodobało się?

Jeśli tak, to zarejestruj się do newslettera aby otrzymywać informacje nowych artykułach oraz akcjach edukacyjnych. Gwarantuję 100% satysfakcji i żadnego spamowania!

, , , , , , , , , , , , , , , , , ,

Dodaj komentarz

Komentarze (5)

Odpowiedz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *

Pin It on Pinterest