1. Wiele bloków catch: obsługa różnych wyjątków
W rzeczywistych programach ten sam blok kodu może zgłaszać różne typy wyjątków. Na przykład podczas odczytu pliku może wystąpić zarówno FileNotFoundException, jak i IOException, a przy parsowaniu danych — również NumberFormatException. Aby poprawnie obsłużyć każdą sytuację, w Javie można użyć kilku następujących po sobie bloków catch.
Składnia
try {
// niebezpieczne operacje
} catch (FileNotFoundException e) {
System.out.println("Nie znaleziono pliku: " + e.getMessage());
} catch (IOException e) {
System.out.println("Błąd wejścia/wyjścia: " + e.getMessage());
} catch (NumberFormatException e) {
System.out.println("Nieprawidłowy format liczby: " + e.getMessage());
}
Ważna uwaga:
Bloki catch powinny iść od bardziej specyficznych do bardziej ogólnych! Jeśli najpierw napiszemy catch (Exception e), to wszystkie pozostałe bloki staną się nieosiągalne, a kompilator zgłosi błąd.
Dlaczego to jest ważne?
- Specyficzna obsługa: Można różnie reagować na różne błędy (np. zasugerować wybór innego pliku lub powtórzenie wprowadzania).
- Czytelność: Kod staje się bardziej zrozumiały — widać, jakich błędów się oczekuje i jak są obsługiwane.
2. catch z wieloma typami (multi-catch)
Czasem dla różnych wyjątków potrzebna jest taka sama obsługa. Na przykład chcesz po prostu wyświetlić komunikat o błędzie lub go zalogować, niezależnie od tego, czy był to błąd odczytu pliku, czy konwersji liczby.
Od Java 7 pojawiła się składnia multi-catch, która pozwala połączyć kilka typów wyjątków w jednym bloku:
try {
// niebezpieczna operacja
} catch (IOException | NumberFormatException e) {
System.out.println("Błąd podczas przetwarzania pliku lub liczby: " + e.getMessage());
}
Jak to działa:
- Typy wyjątków podaje się rozdzielone pionową kreską (|).
- Zmienna e będzie miała typ najbardziej ogólnej klasy bazowej (zwykle Exception).
- Wewnątrz bloku nie można przypisywać nowej wartości do zmiennej e — jest ona automatycznie traktowana jako final.
Przykład:
try {
BufferedReader reader = new BufferedReader(new FileReader("numbers.txt"));
String line = reader.readLine();
int number = Integer.parseInt(line);
System.out.println("Liczba: " + number);
reader.close();
} catch (IOException | NumberFormatException e) {
System.out.println("Błąd: " + e.getMessage());
}
3. Najlepsze praktyki obsługi wyjątków
Nie tłumcie wyjątków
Źle:
try {
// coś niebezpiecznego
} catch (Exception e) {
// nic nie robimy!
}
Dlaczego to jest złe?
Pusty blok catch „wycisza” błąd — tracisz informację o przyczynie awarii, a program staje się niemożliwy do debugowania.
Lepiej: przynajmniej wyświetl komunikat albo zaloguj błąd.
catch (Exception e) {
e.printStackTrace();
}
Jeśli nie możesz obsłużyć wyjątku — przekaż go dalej:
catch (IOException e) {
throw e; // albo throw new RuntimeException(e);
}
Rzucaj jak najbardziej specyficzne wyjątki
Jeśli piszesz własne metody lub API, zawsze rzucaj możliwie najbardziej konkretne wyjątki, a nie po prostu Exception lub RuntimeException. Dzięki temu twój kod i interfejs są bardziej zrozumiałe dla innych programistów.
Przykład:
public void withdraw(double amount) throws InsufficientFundsException {
if (amount > balance) {
throw new InsufficientFundsException("Niewystarczające środki");
}
// ...
}
Nie używaj wyjątków do sterowania przepływem programu
Wyjątki są przeznaczone do obsługi sytuacji wyjątkowych — czyli błędów, które nie powinny występować w normalnym przebiegu działania programu. Nie używaj ich do zwykłej logiki (np. do wyjścia z pętli albo sprawdzania warunków).
Źle:
try {
while (true) {
String line = reader.readLine();
if (line == null) break;
// przetwarzanie wiersza
}
} catch (Exception e) {
// używamy wyjątku do wyjścia z pętli — źle!
}
Dobrze:
String line;
while ((line = reader.readLine()) != null) {
// przetwarzanie wiersza
}
4. Praktyczne przykłady
Przykład: różna obsługa różnych błędów
try {
BufferedReader reader = new BufferedReader(new FileReader("data.txt"));
String line = reader.readLine();
int number = Integer.parseInt(line);
System.out.println("Liczba: " + number);
reader.close();
} catch (FileNotFoundException e) {
System.out.println("Nie znaleziono pliku!");
} catch (IOException e) {
System.out.println("Błąd odczytu pliku!");
} catch (NumberFormatException e) {
System.out.println("W pliku nie ma liczby!");
}
Przykład: multi-catch dla jednakowej obsługi
try {
// coś niebezpiecznego
} catch (IOException | NumberFormatException e) {
System.out.println("Błąd: " + e.getMessage());
}
5. Ważne niuanse i typowe błędy
Błąd nr 1: Nieprawidłowa kolejność bloków catch. Najpierw powinny iść bardziej specyficzne wyjątki, a potem — ogólne. W przeciwnym razie wykonanie nigdy nie dotrze do szerokiego bloku.
Błąd nr 2: Użycie pokrewnych typów w multi-catch. Na przykład catch (IOException | Exception e) się nie skompiluje, ponieważ IOException dziedziczy po Exception.
Błąd nr 3: Próba zmiany zmiennej e w multi-catch. W takim przypadku e jest traktowana jako final — nie można przypisać jej nowej wartości.
Błąd nr 4: Puste bloki catch. Ignorowanie błędów prowadzi do „cichych” błędów. Minimum — wyświetlaj komunikat lub loguj wyjątek.
Błąd nr 5: Łapanie wszystkich Exception naraz. Użycie catch (Exception e) ukrywa rzeczywiste problemy i utrudnia debugowanie. Lepiej łapać tylko oczekiwane wyjątki.
GO TO FULL VERSION