Wyjątki w Javie

W jednym z poprzednich wpisów omówiliśmy operacje wyjścia-wyjścia, które były obsługiwane przez klasę Scanner. Jedną z jej cech jest możliwość wprowadzania danych przez użytkownika przy pomocy klawiatury. Gdybyśmy wyobrazili sobie idealnego użytkownika, to z pewnością zawsze podawałby dane zgodne z oczekiwaniami programisty. Nikt nie jest nieomylny i zawsze ktoś może popełnić błąd (w tym sam twórca np. próbując dostać się do elementu „spoza tablicy”). Sprzymierzeńcem okazuje się jeden z filarów Javy czyli niezawodność.

Co może pójść nie tak?

Podczas działania programu wiele rzeczy może wyjść poza oczekiwania tworzącego aplikację programisty. Każdy użytkownik komputera czy smartfona z pewnością zobaczył irytujący komunikat „program przestał odpowiadać” wymuszający zamknięcie aplikacji dodatkowo usuwając niezapisane efekty pracy.

Naszym zadaniem jako twórcy aplikacji jest takie zabezpieczenie kodu, aby wychwycić możliwie wszystkie sytuacje, które mogą spowodować przerwanie działania programu. Po naszej stronie jest Java, która ostrzega przy różnych ryzykownych procedurach o możliwości rzucenia wyjątku. Dlatego warto dobrze zaznajomić się z dostępną dokumentacją oraz zwracać uwagę jakie wyjątki mogę zostać rzucone gdy wykorzystujemy jakąś metodę klasy z biblioteki standardowej. Podobnie gdy tworzymy własny kod należy przemyśleć jakie sytuacje mogą się zdarzyć i w odpowiednim momencie przechwycić wyjątek. Natomiast jeśli chcemy opracować, z którego będą korzystać inni odpowiednio go udokumentować.

Błędy można podzielić na sprzętowe i programowe. Możemy założyć, że na sprzętowe nie mamy żadnego wpływu, ale możemy je przewidzieć. Mogą to być.

  • Awarie urządzeń lub brak dostępu (urządzenie wyłączone).
  • Brak materiałów eksploatacyjnych (np. papieru w drukarce).

O ile z awarią sprzętu niewiele możemy zrobić tak w przypadku braku papieru należy poinformować użytkownika o zaistniałej sytuacji i dać mu szansę uzupełnić zasobnik pozwalając mu dalej na wykonanie druku zamiast zaciąć cały program lub system.

W przypadku możliwych błędów programowych mamy już większą szansę na popełnienie błędu własnego. Jednakże również mogą wystąpić błędy, które są niezależne od nas a ponadto nie można ich obsłużyć. Są to błędy związane np. ze środowiskiem uruchomieniowym maszyny wirtualnej, albo poważne błędy związane z interfejsem wejścia-wyjścia.

Wreszcie przechodzimy do sytuacji, które możemy, a nawet powinniśmy w pełni obsłużyć czyli błędy użytkownika, takie jak wprowadzanie danych niezgodnych z oczekiwanymi, błędny format wprowadzanych danych, próba odczytu nieistniejącego pliku. Istnieje również kategoria wyjątków zarezerwowana dla programisty czyli np. próba wywołania metody z ze wskaźnika null lub próba odczytania wartości spoza tablicy.

Klasa Throwable i klasyfikacja wyjątków

Na początku hierarchii dziedziczenia wyjątków znajduje się klasa Throwable, która dziedziczy jedynie po klasie Object. To po niej lub po jej podklasach muszą dziedziczyć wszystkie klasy, których obiekty mogą być rzucone jako wyjątki. To samo dotyczy argumentów klauzuli catch – tylko obiekty tych klas mogą być przechwycone. Hierarchię dziedziczenia pokazuje poniższy rysunek.

Hierarchia wyjątków w Javie

Hierarchia wyjątków w Javie

Klasa Error

Poddrzewo z klasą Error oraz jej podklasy są przykładem poważnym problemów, których nie można rozwiązać i niestety jeśli wystąpią nie można ich przechwycić. Dla programisty ważne jest aby wiedzieć o ich istnieniu, natomiast nie trzeba przykładać do niej wielkiej uwagi ze względu na praktyczne brak możliwości działania. Przykładami tej klasy są:

  • IOError – w przypadku poważnych problemów wejścia-wyjścia,
  • VirtualMachineError – w przypadku uszkodzenia maszyny wirtualne Javy (JVM) lub braku zasobów do dalszej pracy.

Klasa Exception

Po klasie Exception dziedziczy klasa RuntimeException. Wyjątki wywodzące się z jej gałęzi są wyjątkami, których się nie sprawdza (ang. unchecked exception) w związku z tym mogą wystąpić podczas działania programu i nie trzeba deklarować, że metoda może rzucić wyjątek. Przykładami tych wyjątków mogą być:

  • NullPoiterException – może go wywołać próba wywołania metody przez zmienną, która wskazuje na wartość null,
  • IndexOutOfBoundsException – próba dostępu do elementy tablicy, który nie istnieje (indeks większy niż rozmiar tablicy),
  • ConcurrentModificationException – wyjątek, który może wystąpić podczas pracy z kolekcjami i dostępie przy pomocy iteratorów.

Wyjątków będących podklasą RuntimeException jest dużo więcej. W razie potrzeby należy sięgnąć do dokumentacji.

Pozostałe klasy wyjątków są do skontrolowania(ang. checked exception) i dziedziczą bezpośrednio po Exception. Są to rozmaite błędy, które nie zostały uwzględnione wcześniej. Przykładami są:

  • IOException – próba otwarcia pliku, który nie istnieje na dysku (FileNotFoundException), próba utworzenia lub dostępu do gniazda (SocketException),
  • ParseException – parsowanie zmiennych, które nie może się udać (np. String „dwa”, który nie może zostać skonwertowany na 2),
  • SQLException – W przypadku braku dostępu do bazy danych.

W następnej części zajmiemy się bardziej praktyczną częścią czyli rzucaniem oraz przechwytywaniem wyjątków.