Spis treści
W rozdziale o wartościach zwracanych przez metody korzystaliśmy z następującego przykładu:
public class WypiszWynikDzielenia { public static void main(String[] args) { wypiszWynikDzielenia(10, 0); wypiszWynikDzielenia(25, 5); } public static void wypiszWynikDzielenia(int x, int y) { if (y == 0) { System.out.println("Nie mozna dzielic przez 0!"); return; } System.out.println("Wynik dzielenia: " + (x / y)); } }
W metodzie wypiszWynikDzielenia wykonujemy sprawdzenie, czy liczba przekazana w argumencie y nie jest przypadkiem równa 0 – w takim przypadku nie możemy wykonać dzielenia.
Załóżmy, że nasza metoda ma zwracać wartość dzielenia, a nie ją wypisywać – zmodyfikujmy nasz przykład:
public class ZwrocWynikDzielenia { public static void main(String[] args) { System.out.println(podziel(10, 0)); System.out.println(podziel(25, 5)); } public static int podziel(int x, int y) { return x / y; } }
Metodę wypiszWynikDzielenia zastąpiliśmy metodą podziel, która zamiast wypisywać wynik na ekran, zwraca go. Jaki będzie wynik działania powyższego kodu? Na ekranie zobaczymy komunikat o błędzie dzielenia przez zero:
Ten błąd to właśnie wyjątek, który został spowodowany nieprawidłowym działaniem naszego programu – w tym przypadku, Maszyna Wirtualna Java poinformowała nas, że wystąpił błąd dzielenia przez zero, a ten konkretny wyjątek nazywa się ArithmeticException.
Wyjątki (exceptions) to sytuacje, w których coś w programie poszło nie tak. Gdy zajdzie taka sytuacja, mówimy, że został rzucony wyjątek. Wyjątki mogą być rzucane zarówno przez Maszynę Wirtualna Java, jak i przez nas – programistów – co zobaczymy w dalszej części rozdziału.
Bardzo ważną cechą wyjątków jest to, że są to tak naprawdę klasy – mają one swoją nazwę, konstruktory, pola i metody. Co cechuje klasę, że może być traktowana jako wyjątek? Klasa taka musi rozszerzać klasę Throwable lub jedną z jej pochodnych, o czym wkrótce dokładniej sobie opowiemy. Rzucanie wyjątków sprowadza się do utworzenia słowem kluczowym new obiektu konkretnej klasy wyjątku i "rzucenie" go za pomocą słowa kluczowego throw. Zajmiemy się tymi zagadnieniami w kolejnych rozdziałach.
Z wyjątkami spotkaliśmy się już w poprzednich rozdziałach – widzieliśmy m. in. wyjątki:
- StringIndexOutOfBoundsException – gdy próbowaliśmy odnieść się do znaku w zmiennej typu String za pomocą metody charAt przekazując indeks znaku wychodzący poza zakres stringu,
- ArrayIndexOutOfBoundsException – gdy odnosiliśmy się do nieistniejącego elementu tablicy,
- NullPointerException – gdy próbowaliśmy odnosić się do pól bądź metod niezainicjalizowanego obiektu, tzn. gdy zmienna typu złożonego wskazywała na null.
Inne sytuacje, w których moglibyśmy natknąć się na wyjątek, to na przykład:
- podanie ujemnego wieku podczas tworzenia obiektu typu Osoba,
- próba otwarcia pliku, który nie istnieje,
- zerwanie połączenia z internetem podczas próby wysłania danych na serwer,
- podanie nieprawidłowego hasła podczas łączenia się do serwera baz danych,
- i wiele innych.
Sytuacje wyjątkowe możemy obsługiwać dzięki mechanizmowi łapania wyjątków, który poznamy w tym rozdziale.
Stack trace¶
Zauważmy w przykładzie powyżej, jak Maszyna Wirtualna Java prezentuje wyjątek:
Po rodzaju wyjątku i skojarzonym z nim komunikatem w pierwszej linii, następują informacje o ścieżce wykonania programu, która doprowadziła do występienia tego wyjątku.
Jest to tzw. stack trace – ścieżka wykonań metod, które doprowadziły do błędu. Często będziemy analizować stack trace'y programując w Javie.
Stack trace powinno się śledzić się od dołu, ponieważ prezentowana w nim kolejność metod jest odwrotna do kolejności wykonywania tych metod – ostatnia metoda (ta na dole stack trace'a) została wywołana jako pierwsza, a ta na samej górze – jako ostatnia – i to w niej rzucony został wyjątek.
W praktyce jednak patrzymy zazwyczaj na kilka pierwszych linii stack trace'a, bo zazwyczaj wystarczają nam one do zrozumienia dlaczego, a przynajmniej gdzie, wystąpił wyjątek.
W naszym przypadku najpierw wywołana została metoda main z klasy ZwrocWynikDzielenia:
W nawiasach mamy dodatkowo podany plik, z którego klasa pochodzi, a także linię kodu, w której wykonanie metody przeszło do kolejnej metody – ta metoda, jak widzimy patrząc dalej na stack trace, to podziel:
Więcej metod nie zostało wywołanych – oznacza to, że wyjątek został rzucony właśnie w metodzie podziel. Dodatkowo mamy także podany numer 8 w nawiasach – to numer linii programu (a nie linii metody), w której wyjątek został rzucony. Są to bardzo przydatne informacje dla nas, programistów, podczas analizy błędów zaistniałych w naszych programach – dzięki stack trace'om łatwiej znaleźć miejsce, gdzie program zadziałał nieprawidłowo.
Jeżeli spojrzymy ponownie na kod naszej klasy:
public class ZwrocWynikDzielenia { public static void main(String[] args) { System.out.println(podziel(10, 0)); System.out.println(podziel(25, 5)); } public static int podziel(int x, int y) { return x / y; } }
to zobaczymy, że linia nr 3 umieszczona w nawiasach w stack trace odnosi się do:
System.out.println(podziel(10, 0));
a linia 8 do:
return x / y;
Oznaczenia linii w stack trace zgadzają się z wykonaniem programu, które doprowadziło do zaistnienia wyjątku ArithmeticException:
- program zostaje uruchomiony – rozpoczyna się wykonywanie metody main,
- w pierwszej linii metody main (zauważmy, że jest to jednocześnie trzecia linia całego programu) następuje wywołanie metody podziel z argumentami 10 i 0,
- wykonanie programu przechodzi do metody podziel,
- z racji tego, że podaliśmy 0 jako argument y, Maszyna Wirtualna Java rzuca wyjątek ArithmeticException, gdy próbujemy wykonać dzielenie przez 0 – dzieje się to w ósmej linii programu,
- program kończy działanie, a na ekran zostaje wypisany zaistniały błąd: typ wyjątku, jego komunikat, oraz omówiony już stack trace.