Menu

Wyjątki w Javie: błędy, stack trace i rzucanie wyjątków

Czym jest wyjątek w Javie, jak czytać stack trace, czym różnią się wyjątki checked i unchecked, jak wygląda hierarchia wyjątków i jak rzucać własne.

Na tej stronie są działające edytory: edytuj, uruchamiaj i od razu zobacz wynik.

Czym jest wyjątek

Wyjątek to sposób, w jaki Java mówi: „coś poszło nie tak i nie mogę normalnie kontynuować”. Zamiast zwracać złą wartość albo po cichu psuć dane, środowisko uruchomieniowe tworzy obiekt wyjątku opisujący problem i go rzuca. W tym miejscu normalne wykonanie się zatrzymuje, a Java zaczyna szukać kodu, który wie, jak poradzić sobie z sytuacją.

Jeśli nikt go nie obsłuży, wyjątek dociera na szczyt programu, JVM wypisuje stack trace (ślad stosu), a proces kończy się niezerowym kodem wyjścia.

Zauważ, że "after" nigdy się nie wypisuje. W chwili obliczenia numbers[5] zostaje rzucony ArrayIndexOutOfBoundsException, a reszta main jest porzucana. Sięgnięcie poza ostatni poprawny indeks tablicy to jeden z najczęstszych sposobów wywołania wyjątku.

Czytanie stack trace

Gdy wyjątek nie zostanie obsłużony, dostajesz wynik podobny do tego. Wygląda groźnie, ale to po prostu lista:

Exception in thread "main" java.lang.ArithmeticException: / by zero
	at Main.divide(Main.java:8)
	at Main.main(Main.java:4)

Czytaj go od góry do dołu:

  • Pierwszy wiersz to typ (ArithmeticException) i komunikat (/ by zero).
  • Każdy wiersz at ... to ramka stosu. Najwyższa wskazuje miejsce, w którym wyjątek faktycznie rzucono: divide w wierszu 8. Pod nią jest metoda wywołująca, main w wierszu 4.

Najpierw patrzysz na najwyższy wiersz at. Ramki pod nim odpowiadają na pytanie „jak tu trafiliśmy?”.

Uruchom ten kod, a ślad wskaże dokładnie wiersz a / b, a potem pokaże main jako metodę wywołującą. Ten łańcuch wywołań to najprzydatniejsze narzędzie do debugowania, jakie Java daje za darmo.

Hierarchia wyjątków

Każdy wyjątek jest obiektem i wszystkie pochodzą od Throwable. Liczą się dwie gałęzie:

  • Error: poważne problemy zgłaszane przez JVM, takie jak OutOfMemoryError czy StackOverflowError. Zasadniczo nie przechwytuje się ich.
  • Exception: problemy, które program może rozsądnie przewidzieć i obsłużyć. W jej wnętrzu jest RuntimeException, rodzic codziennych błędów, takich jak NullPointerException.
Throwable
├── Error                 (don't catch: OutOfMemoryError, StackOverflowError)
└── Exception
    ├── IOException        (checked)
    ├── SQLException       (checked)
    └── RuntimeException   (unchecked)
        ├── NullPointerException
        ├── ArithmeticException
        └── ArrayIndexOutOfBoundsException

Ponieważ to prawdziwe klasy, wyjątek może nieść komunikat, a ty możesz wypytać go o szczegóły:

getMessage() zwraca tekst po dwukropku ze stack trace, a getClass().getSimpleName() podaje nazwę typu wyjątku.

Checked a unchecked

To rozróżnienie najczęściej myli początkujących.

  • Wyjątki unchecked rozszerzają RuntimeException. Zwykle oznaczają błąd w kodzie: nieoczekiwany null, zły indeks, dzielenie przez zero. Kompilator nie zmusza do ich obsługi.
  • Wyjątki checked rozszerzają Exception, ale nie RuntimeException (na przykład IOException). Opisują sytuacje, na które nie masz wpływu: brakujący plik, zerwane połączenie sieciowe. Kompilator zmusza cię, by je przechwycić albo zadeklarować.

Jeśli metoda może rzucić wyjątek checked, musi to zadeklarować przez throws, a każda metoda wywołująca musi się z nim uporać:

Usuń throws Exception z main, a kod nawet się nie skompiluje: tak kompilator egzekwuje kontrakt wyjątków checked. Wyjątki unchecked nigdy tego nie wymagają.

Rzucanie własnych wyjątków

Nie tylko reagujesz na wyjątki, możesz też je zgłaszać. Użyj throw z nowym obiektem wyjątku, aby zasygnalizować, że argument lub stan jest nieprawidłowy. To znacznie lepsze niż zwracanie magicznej wartości w rodzaju -1 w nadziei, że wywołujący ją sprawdzi.

Zawsze dodawaj komunikat, który wyjaśnia, co było nie tak, a najlepiej podaje też błędną wartość. Za jakiś czas, czytając stack trace, docenisz to. IllegalArgumentException i IllegalStateException to dwa wyjątki, po które najczęściej sięga się przy walidacji danych wejściowych.

Częste pułapki

  • Połykanie wyjątków. Przechwycenie wyjątku i niezrobienie niczego (pusty blok) ukrywa błędy. Przynajmniej go zaloguj; zwykle trzeba go obsłużyć albo rzucić ponownie.
  • Zbyt szerokie przechwytywanie Exception. Przechwycenie bazowego Exception (albo, co gorsza, Throwable) może zamaskować problemy, których nie zamierzałeś obsługiwać. Przechwytuj konkretny typ, którego się spodziewasz.
  • Czytanie śladu od dołu. Najważniejsza ramka to najwyższy wiersz at, a nie najniższy. Zaczynaj od niego.
  • Mylenie Error z Exception. Nie próbuj ratować się po OutOfMemoryError ani StackOverflowError; zamiast tego usuń przyczynę.

Dalej: try-catch

Wiesz już, czym są wyjątki i jak je czytać. Następna strona pokazuje, jak je faktycznie obsługiwać: jak umieścić ryzykowny kod w bloku try, przywrócić działanie w catch i wykonać kod sprzątający w finally, aby program działał dalej zamiast się wysypać.

Najczęściej zadawane pytania

Czym jest wyjątek w Javie?

Wyjątek to obiekt, który opisuje problem wykryty podczas działania programu, na przykład dzielenie przez zero, odwołanie do indeksu za końcem tablicy albo wywołanie metody na null. Gdy problem wystąpi, Java rzuca wyjątek: przerywa normalny przebieg i szuka kodu, który potrafi go obsłużyć. Jeśli nic go nie obsłuży, program wypisuje stack trace i kończy działanie.

Czym różni się wyjątek checked od unchecked w Javie?

Wyjątki checked (podklasy Exception, ale nie RuntimeException, np. IOException) trzeba przechwycić albo zadeklarować przez throws, czego pilnuje kompilator. Wyjątki unchecked (podklasy RuntimeException, np. NullPointerException, ArrayIndexOutOfBoundsException) zwykle sygnalizują błędy w kodzie i nie wymagają deklaracji. Error (np. OutOfMemoryError) też jest unchecked i zasadniczo nie służy do przechwytywania.

Jak czytać stack trace w Javie?

Czytaj od góry. Pierwszy wiersz podaje typ wyjątku i komunikat (np. java.lang.ArithmeticException: / by zero). Każdy wiersz at ... poniżej to ramka stosu z metodą, plikiem i numerem wiersza, zaczynając od miejsca rzucenia wyjątku i cofając się przez kolejne wywołujące metody. Najwyższy wiersz at to prawie zawsze pierwsze miejsce, w które warto zajrzeć.

Ilustracja języków programowania w Coddy

Ucz się programowania z Coddy

ZACZNIJ