1. Множинний catch: обробка різних винятків
У реальних програмах один і той самий блок коду може викидати різні типи винятків. Наприклад, під час читання файлу може виникнути як FileNotFoundException, так і IOException, а під час розбору даних — ще й NumberFormatException. Щоб коректно обробити кожен випадок, у Java можна використовувати кілька блоків catch поспіль.
Синтаксис
try {
// небезпечні операції
} catch (FileNotFoundException e) {
System.out.println("Файл не знайдено: " + e.getMessage());
} catch (IOException e) {
System.out.println("Помилка введення-виведення: " + e.getMessage());
} catch (NumberFormatException e) {
System.out.println("Некоректний формат числа: " + e.getMessage());
}
Важливий момент:
Блоки catch мають розташовуватися від конкретніших до загальніших. Якщо спочатку написати catch (Exception e), то всі інші блоки стануть недосяжними, і компілятор згенерує помилку.
Чому це важливо?
- Конкретна обробка: Можна по-різному реагувати на різні помилки (наприклад, запропонувати вибрати інший файл або повторити введення).
- Зрозумілість коду: Код стає зрозумілішим — одразу видно, які помилки очікуються й як вони обробляються.
2. catch з кількома типами (multi-catch)
Іноді для різних винятків потрібна однакова обробка. Наприклад, ви хочете просто вивести повідомлення про помилку або залогувати її, незалежно від того, чи була це помилка читання файлу або перетворення числа.
Починаючи з Java 7, з’явився синтаксис multi-catch, який дозволяє об’єднати кілька типів винятків в одному блоці:
try {
// небезпечна операція
} catch (IOException | NumberFormatException e) {
System.out.println("Помилка під час обробки файлу або числа: " + e.getMessage());
}
Як це працює:
- Через вертикальну риску (|) зазначаються типи винятків.
- Змінна e має тип спільного базового типу перелічених винятків (найчастіше — Exception).
- Усередині блоку не можна присвоювати нове значення змінній e — її неявно вважають final.
Приклад:
try {
BufferedReader reader = new BufferedReader(new FileReader("numbers.txt"));
String line = reader.readLine();
int number = Integer.parseInt(line);
System.out.println("Число: " + number);
reader.close();
} catch (IOException | NumberFormatException e) {
System.out.println("Помилка: " + e.getMessage());
}
3. Найкращі практики обробки винятків
Не придушуйте винятки
Погано:
try {
// щось небезпечне
} catch (Exception e) {
// нічого не робимо!
}
Чому це погано?
Порожній блок catch «глушить» помилку — ви втрачаєте інформацію про причину збою, тому програму неможливо налагодити.
Краще: принаймні виведіть повідомлення або залогуйте помилку.
catch (Exception e) {
e.printStackTrace();
}
Якщо ви не можете обробити виняток — прокиньте його далі:
catch (IOException e) {
throw e; // або throw new RuntimeException(e);
}
Викидайте максимально конкретні винятки
Якщо ви пишете свої методи чи API, завжди викидайте максимально конкретні винятки, а не просто Exception або RuntimeException. Це робить ваш код і інтерфейс зрозумілішими для інших розробників.
Приклад:
public void withdraw(double amount) throws InsufficientFundsException {
if (amount > balance) {
throw new InsufficientFundsException("Недостатньо коштів");
}
// ...
}
Не використовуйте винятки для керування потоком програми
Винятки призначені для обробки виняткових ситуацій — тобто помилок, які не повинні траплятися в нормальному перебігу роботи програми. Не використовуйте їх для звичайної логіки (наприклад, для виходу з циклу або перевірки умов).
Погано:
try {
while (true) {
String line = reader.readLine();
if (line == null) break;
// обробка рядка
}
} catch (Exception e) {
// використовуємо виняток для виходу з циклу — погано!
}
Добре:
String line;
while ((line = reader.readLine()) != null) {
// обробка рядка
}
4. Практичні приклади
Приклад: обробка різних помилок по-різному
try {
BufferedReader reader = new BufferedReader(new FileReader("data.txt"));
String line = reader.readLine();
int number = Integer.parseInt(line);
System.out.println("Число: " + number);
reader.close();
} catch (FileNotFoundException e) {
System.out.println("Файл не знайдено!");
} catch (IOException e) {
System.out.println("Помилка читання файлу!");
} catch (NumberFormatException e) {
System.out.println("У файлі не число!");
}
Приклад: multi-catch для однакової обробки
try {
// щось небезпечне
} catch (IOException | NumberFormatException e) {
System.out.println("Помилка: " + e.getMessage());
}
5. Важливі нюанси та типові помилки
Помилка № 1: Неправильний порядок блоків catch. Спочатку мають іти конкретніші винятки, а потім — загальні. Інакше специфічні блоки стануть недосяжними.
Помилка № 2: Використання споріднених типів у multi-catch. Наприклад, catch (IOException | Exception e) не скомпілюється, тому що IOException успадковується від Exception.
Помилка № 3: Спроба змінити змінну e у multi-catch. У цьому випадку e вважається final — присвоїти їй нове значення не можна.
Помилка № 4: Порожні catch-блоки. Ігнорування помилок призводить до «тихих» багів. Мінімум — виводьте повідомлення або залогуйте виняток.
Помилка № 5: Ловити всі Exception підряд. Використання catch (Exception e) приховує реальні проблеми та ускладнює налагодження. Краще ловити лише очікувані винятки.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ