Wyjątki są nierozłączną częścią programowania i choćbyśmy nie wiem jak się starali, to i tak nie uchronimy się przed ich pojawieniem. Jednak możemy odpowiednio zabezpieczyć się na wypadek wystąpienia wyjątku, czyli „wyłapać” wyjątek za pomocą bloków try
, catch
i finally
.
Wyobraźmy sobie program, który obsługuje dane obywateli. Obiekt Citizen
ma między innymi pola z imieniem, nazwiskiem i adresem. Obiekt Address
natomiast ma pole miasto (obiekt klasy City
). Poniższy kod powinien więc zwrócić nam miasto zamieszkania obywatela.
Citizen citizen = new Citizen();
City city = citizen.getAddress().getCity();
Czy jednak każdy obywatel ma przypisany jakiś adres? Początkowo może wydawać się, że tak. I w danych testowych testowych też możemy nie trafić na taki przypadek, a jednak przecież to oczywiste, że są obywatele bez przypisanego jakiegokolwiek adresu. Co wtedy zwróci nasz kod? Mniej więcej coś takiego:
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "Address.getCity()" because the return value of "Citizen.getAddress()" is null
Nie obsłużyliśmy przypadku, w którym wystąpić mógł popularny NPE. Działanie programu zostało przerwane, a być może adres w ogóle nie jest kluczowy do żadnej funkcjonalności. I tu właśnie można użyć jako jedno z możliwych rozwiązań blok try
.
Citizen citizen = new Citizen();
City city;
try {
city = citizen.getAddress().getCity();
} catch (NullPointerException ex) {
e.printStackTrace();
}
Dzięki temu program nie zostanie przerwany, lecz oczywiście nie będziemy mieć w takich przypadkach wypełnionego pola City. Należy jednak pamiętać, że dany blok catch
obsługuje tutaj tylko ten określony wyjątek NullPointerException
. Każdy inny spowoduje przerwanie działanie programu. Chyba nie do końca zalecanym, lecz spotykanym rozwiązaniem jest wychwycenie w bloku catch
wszystkich wyjątków, czyli catch (Exception ex) {}
.
Czym jest wspomniany blok finally
? To blok, który zostanie wykonany niezależnie od tego czy wyjątek zostanie rzucony, czy nie. Jest to idealne rozwiązanie w przypadkach, w których wymagana jest jakaś akcja np. zamknięcie FileReadera
. Blok finally
nie jest wymagany po bloku try
, podobnie jak catch
. Musi zostać bowiem wykorzystana jedna z tych opcji. Poniżej przykład z blokiem catch
i finally
z osobnym blokiem try-catch
. Niestety stosując takie rozwiązania każdy możliwy przypadek wystąpienia wyjątku należy obsłużyć. Jednak i na to znajdzie się rozwiązanie, o czym poniżej.
BufferedReader reader = null;
try {
reader = new BufferedReader(new FileReader("citizen.txt"));
citizen.setName(reader.readLine());
} catch (IOException e) {
e.printStackTrace();
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
try-with-resources
to blok, który pozwala oszczędzić parę linijek kodu. Dzięki umieszczeniu naszego readeara w nawiasach po słowie try
zostanie on nawet automatycznie zamknięty. Nie musimy więc ręcznie wywołać metody close()
. Prawda, że prościej?
try (BufferedReader reader = new BufferedReader(new FileReader("citizen.txt"))) {
citizen.setName(reader.readLine());
} catch (IOException e) {
e.printStackTrace();
}
Co warto zapamiętać z obsługi wyjątków blokami try
, catch
, finally
lub try-with-resources
?
- są one pomocne, zwłaszcza dla wyjątków unchecked (tych, których obsługa nie jest konieczna);
- po bloku
try
musi nastąpić blokcatch
lubfinally
; - blok
finally
zostanie wykonany niezależnie czy wyjątek zostanie rzucony, czy nie; - blok
try-with-resources
pozwala ograniczyć liczbę linijek kodu i odpowiada za zamknięcie wskazanego zasobów (resources);
Jeżeli macie uwagi bądź pytania, piszcie w komentarzach.