– Cześć, Amigo. Dzisiaj czeka Cię ciekawa lekcja. Opowiem Ci o wyjątkach. Wyjątki (ang. exception) są specjalnym mechanizmem, który pozwala obsługiwać błędy w programie. Oto kilka przykładów błędów, które mogą pojawić się w programie:

1. Program może próbować zapisać plik, podczas gdy twardy dysk jest kompletnie zapełniony.

2. Program może próbować wywołać metodę na zmiennej przechowującej referencję o wartości null.

3. Program może próbować dzielić liczbę przez 0.

Wszystkie te akcje kończą się błędami. Zazwyczaj skutkuje to natychmiastowym przerwaniem działania programu, ponieważ w tym wypadku kontynuacja wykonywania kodu nie ma już sensu.

– Dlaczego?

– A jaki jest sens w toczeniu opony, jeśli samochód wypadł z drogi bądź spadł z klifu?

– Czyli program powinien przestać działać, tak?

– Tak. A przynajmniej tak do tej pory się działo. Wystąpienie jakiegokolwiek błędu przerywało działanie programu.

– To mądre podejście.

– Ale czy nie byłoby lepiej spróbować jednak kontynuować jego działanie?

– Tak. Załóżmy, że napisałeś w Wordzie długi tekst i zapisałeś go. Co jeśli operacja zapisywania się nie powiodła, ale Ty nie otrzymałeś o tym żadnej informacji? I piszesz sobie dalej. To nie miałoby sensu, prawda?

– Tak.

– Programiści mają na to interesujące rozwiązanie: każda funkcja ma zwracać status swojej pracy. 0 oznacza, że wszystko się powiodło. A każda inna wartość mówi nam, że pojawił się jakiś błąd i że zwracana wartość spowodowała błąd w funkcjonowaniu kodu.

– Oczywiście, to rozwiązanie ma także swoje wady. Po każdym (!) wywołaniu funkcji musisz sprawdzić zwracany kod (liczbę). Po pierwsze, to bardzo niewygodne: kod obsługujący błędy jest wykonywany rzadko, ale musi zostać umieszczony wszędzie. Po drugie, funkcje często zwracają różne wartości – tylko co dalej z nimi zrobić?

– Racja. Też o tym pomyślałem.

– Tak było do czasu, aż został stworzony mechanizm obsługi błędów i wyjątków. Oto, jak to działa:

1. Kiedy pojawia się błąd, Maszyna Java tworzy specjalny obiekt – wyjątek – w którym zapisuje wszystkie informacje o błędzie. Dla różnych błędów istnieją różne wyjątki.

2. Wyjątek sprawia, że program natychmiast wychodzi z bieżącej funkcji, potem z następnej i tak dalej – aż zostanie tylko metoda main. Następnie program zostaje przerwany. Programiści mówią także, że Maszyna Java „rozwija stos wywołań”.

– Przecież powiedziałeś, że nie zawsze program zostaje przerwany.

– Tak, ponieważ istnieje sposób, żeby obsłużyć wyjątek. Możemy napisać specjalny kod w odpowiednim miejscu, który obsłuży wyjątki, na których nam zależy i będzie umiał coś z nimi zrobić. To jest niezwykle ważne.

– Aby to zrobić, trzeba użyć specjalnego konstruktu, twierdzenia try-catch. Oto, jak ono działa:

Przykład programu, który obsługuje wyjątek (dzielenie przez 0) i pracuje dalej.
public class ExceptionPrzyklad2
{
    public static void main(String[] args)
    {
        System.out.println("Program rozpoczyna pracę");

        try
        {
            System.out.println("Przed wywołaniem method1");
            method1();
            System.out.println("Po wywołaniu method1. Ten komunikat nigdy nie zostanie wyświetlony");
        }
        catch (Exception e)
        {
           System.out.println("Wyjątek zostaje obsłużony");
        }

        System.out.println("Program cały czas pracuje");
    }

    public static void method1()
    {
        int a = 100;
        int b = 0;
        System.out.println(a / b);
    }
}
Wynik na ekranie:

Program zaczyna działać
Przed wywołaniem method1
Wyjątek zostaje obsłużony
Program wciąż działa

– A co z wyświetleniem „Po wywołaniu method1 Ten komunikat nigdy nie zostanie wyświetlony” na ekranie?

– Cieszę się, że o to pytasz. W linii 25 dzielimy przez 0, co skutkuje błędem – wyjątkiem. Maszyna Java tworzy obiekt ArithmeticException z informacją o błędzie. Obiekt jest wyjątkiem.

– Wyjątek pojawia się wewnątrz metody method1. To powoduje natychmiastowe przerwanie metody. Skutkowałoby to przerwaniem metody main, gdyby nie blok try-catch.

– Jeśli wyjątek pojawi się wewnątrz bloku try, to zostanie obsłużony przez blok catch. Reszta kody w bloku try nie będzie wtedy wykonywana. Zamiast zostanie wykonany kod z bloku catch.

– Nic nie rozumiem.

– Innymi słowy, kod działa w ten sposób:

1. Jeśli wyjątek pojawi się wewnątrz bloku try, kod przestaje być obsługiwany w miejscu wystąpienia wyjątku, a zaczyna się wykonywanie bloku catch.

2. Jeśli nie pojawi się żaden błąd, to blok try jest wykonywany do końca, a blok catch nie jest wykonywany."

– Hę?

– Wyobraź sobie, że po każdym wywołaniu metody sprawdzamy, czy metoda zwraca oczekiwaną wartość czy może zostaje nagle przerwana w wyniku wyjątku. Jeśli wystąpił wyjątek, przechodzimy do wykonywania bloku catch (jeśli takowy istnieje), aby obsłużyć wyjątek. Jeśli nie ma żadnego bloku catch, przerywamy bieżącą metodę, a metoda, która została przez nas wywołana przeprowadza to samo sprawdzanie.

– Teraz już chyba rozumiem.

– Doskonale.

– Co oznacza „Exception” wewnątrz twierdzenia catch?

Wszystkie wyjątki są klasami, które dziedziczą po klasie Exception. Możemy obsłużyć określony wyjątek przez wskazanie klasy wyjątku w bloku catch bądź możemy obsłużyć wszystkie wyjątki, wskazując ich wspólną klasę macierzystą – Exception. Następnie możemy uzyskać wszystkie niezbędne informacje o błędzie od zmiennej e (przechowuje ona referencję do obiektu wyjątku).

– Super! Jeśli w mojej metodzie pojawiają się różne błędy, to czy mogę przetwarzać je na różne sposoby?

– Nie tylko możesz, ale powinieneś. Możesz to zrobić w ten sposób:

Przykład:
public class ExceptionPrzyklad2
{
    public static void main(String[] args)
    {
        System.out.println("Program rozpoczyna pracę");

        try
        {
            System.out.println("Przed wywołaniem method1");
            method1();
            System.out.println("Po wywołaniu method1. Ten komunikat nigdy nie zostanie wyświetlony");
        }
        catch (NullPointerException e)
        {
           System.out.println("Referencja null. Wyjątek został obsłużony");
        }
        catch (ArithmeticException e)
        {
            System.out.println("Dzielenie przez zero. Wyjątek został obsłużony");
        }
        catch (Exception e)
        {
            System.out.println("Jakiekolwiek inne błędy. Wyjątek został obsłużony");
        }

        System.out.println("Program cały czas pracuje");
    }

    public static void method1()
    {
        int a = 100;
        int b = 0;
        System.out.println(a / b);
    }
}

– Blok try może zostać połączony z kilkoma blokami catch, z których każdy będzie obsługiwał określone typy wyjątków.

– Myślę, że rozumiem. Nie umiem jeszcze sam tego napisać, ale jeśli zobaczę to w kodzie, to nie będę już przerażony.