Rzucanie i przechwytywanie wyjątków w Javie

W poprzedniej części wpisu na temat wyjątków w Javie przyjrzeliśmy się klasom, które odpowiadają za wyjątki oraz błędy w Javie. Poznaliśmy wiele przykładów gotowych klas oraz podział na wyjątki kontrolowane oraz niekontrolowane. Jednakże sama znajomość klas nie wystarczy i należy poznać techniki rzucania oraz obsługi wyjątków. W tym celu użyjemy słów kluczowych throws, try oraz catch.
Wyjątki kontrolowane to wszystkie klasy z gałęzi Exception, które nie dziedziczą po RuntimeException. To właśnie one będą przedmiotem zainteresowania w dzisiejszej lekcji.

Przechwytywanie a przekazywanie wyjątków

Wyjątki możemy kontrolowane możemy potraktować dwojako – tzn. możemy je obsłużyć lub przekazać dalej w celu obsłużenia. Kiedy powinniśmy sami się zająć wyjątkiem? Istnieje niepisana zasada „throw early, catch late” (zgłaszaj wcześnie, przechwytuj późno). Za tą zasadą przemawia fakt, że im później próbujemy przechwycić wyjątek, tym więcej wiemy na temat możliwości jego wystąpienia. Natomiast jeżeli nie mamy wystarczająco informacji na temat jak aplikacja powinna zachować się powinniśmy tylko poinformować o tym, że metoda może rzucić dany wyjątek.

Przykładem może być metoda, która wczytuje pliki do pamięci. Należy zaznaczyć, że plik, który został wskazany może nie istnieć na dysku (FileNotFoundException) natomiast obsługę zostawić metodzie, która będzie korzystać z tego pliku.

Rzucanie wyjątków

Najpierw należy wybrać klasę wyjątku możliwie jak najlepiej opisującą dany problem. W tym celu należy zaznajomić się z dokumentacją.

Załóżmy, że nasza metoda ma wczytać plik z dysku, a następnie go zmodyfikować. Już pierwszy krok jest obarczony ryzykiem, że wskazana ścieżka niewłaściwa lub plik nie istnieje. W związku z tym informujemy, że taka sytuacja może nastąpić obok deklaracji metody przy pomocy słowa kluczowego throws oraz klasy wyjątku.

Aby rzucić wyjątkiem należy użyć słowa kluczowego throw.

Zwróćmy uwagę, że metoda rzuca obiekt typu IOException w związku z tym użyto słowa kluczowego new. Powyższą instrukcję można rozpisać na dwie linijki czyli tworzą zmienną i dopiero podać ją po słowie kluczowym, jednakże ten sposób wydaje się być bardziej zwięzły.

Istnieje też inny konstruktor, który pozwala opisać, co poszło nie tak.

Przechwytywanie wyjątków

Wiele metod z biblioteki standardowej Javy ma wyszczególnione wyjątki, które może rzucić w przypadku niepowodzenia. Każdy z tych wyjątków należy przechwycić, gdyż w przeciwnym wypadku spowodują zatrzymanie aktualnego wątku. To samo tyczy się naszych metod

Obsługa wyjątku często wymaga odpowiedniego rozplanowania kodu. Oczywiście w niektórych przypadkach możemy po prostu zwrócić dane ze stosu (wtedy wystarczy jedna linijka kodu), natomiast w niektórych przypadkach należy dać użytkownikowi szansę na poprawę i pozwolić mu ponownie wykonać dany fragment kodu.

Pamiętamy również o zasadzie nie przechwytywania wyjątków dziedziczących po klasie RuntimeException.

blok try-catch

Ogólna konstrukcja bloku try-catch wygląda następująco.

W pierwszym bloku (try) należy umieścić fragment kodu, który potencjalnie może spowodować wystąpienie wyjątku w programie. Jeżeli istnieje więcej niż jedna ryzykowna procedura należy ją umieścić w tym samym bloku try. W przypadku wystąpienia problemu reszta kodu w bloku try jest ignorowana i sterowanie zostaje przekazane do bloku catch. Tam następuje próba znalezienia procedur awaryjnych na podstawie typu wyrzuconego przez metodę błędu.

Kilka klas wyjątków

Co zrobić w przypadku, gdy metoda może rzucić wyjątki kilku klas? Wszystko zależy od przemyślanego na samym początku sposobie obsługi wyjątków. Jeżeli kilka typów wyjątków może zostać obsłużona w identyczny sposób, to należy je wypisać w tym samym bloku catch po operatorze |.

Natomiast jeżeli obsługa błędu wymaga osobnej procedury należy dodać kolejny blok catch.

Zwróćmy uwagę na kolejność bloków catch. W hierarchii dziedziczenia EOFException jest podklasą IOException. W związku z tym gdybyśmy zamienili kolejność, to wyjątek zostałby obsłużony w nieodpowiednim bloku!

Blok finally

Kod w bloku finally zapewnia jego wykonanie bez względu na powodzenie operacji w bloku try. Jest to pożądana cecha np. w przypadku otwarcia pliku czy dostępu do bazy danych dzięki czemu można bezpiecznie zamknąć połączenie. Kod znajdujący się w blok finally jest wykonywany zawsze bez względu na poprawność działania aplikacji.

Wymagane bloki oraz ich kolejność

Istnieją trzy typy bloków.

  • try-catch…
  • try-catch-…-catch-finally
  • try-finally

Można użyć dowolnej konstrukcji, jednakże należy pamiętać o poszczególnych cechach każdego z bloków. Ważna jest także ich kolejność.

W następnej części opiszemy jak tworzyć własne wyjątki.