Чи можна кілька catch блоків для одного try?
Java дозволяє використовувати кілька catch блоків для одного try:
Junior Level
Так, можна!
Java дозволяє використовувати кілька catch блоків для одного try:
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.FileNotFoundException;
public class FileProcessor {
public String readFile(String path) {
try {
return Files.readString(Paths.get(path));
} catch (FileNotFoundException e) {
// Приватний випадок — файл не існує
return "default content";
} catch (IOException e) {
// Більш загальний — інші IO помилки
throw new ServiceException("Failed to read file: " + path, e);
}
}
}
Правило: від приватного до загального
Блоки catch мають йти від конкретних до загальних:
// Правильно
try { ... }
catch (FileNotFoundException e) { } // Приватний
catch (IOException e) { } // Більш загальний
catch (Exception e) { } // Найзагальніший
// Неправильно — не скомпілюється!
try { ... }
catch (Exception e) { } // Перехопить все!
catch (FileNotFoundException e) { } // Ніколи не виконається
ЧОМУ порядок важливий: механізм “first match wins”
Компілятор перевіряє порядок catch блоків на етапі компіляції. Коли ви пишете кілька catch, компілятор будує ієрархію типів і перевіряє, що кожен наступний catch не є підтипом попереднього.
На рівні компілятора:
- Компілятор аналізує кожен catch блок зверху вниз.
- Для кожного catch він перевіряє: «Чи є вже вище catch, який є супертипом поточного?»
- Якщо так — помилка компіляції
"exception X has already been caught", бо код у нижньому catch блоці недосяжний (unreachable code, JLS 14.21). - Якщо ні — catch додається до Exception Table.
На рівні JVM (рантайм):
- При викиданні винятку JVM проходить Exception Table зверху вниз.
- Перший збіг за типом (або супертипом) захоплює управління.
- Решта catch блоків не перевіряються — навіть якщо вони теж підійшли б.
Це називається “first match wins”. Тому порядок визначає, який обробник отримає управління.
try { ... }
catch (FileNotFoundException e) { ... } // #1: перевіряється першим
catch (IOException e) { ... } // #2: перевіряється, тільки якщо #1 не підійшов
Якщо у try кинуто FileNotFoundException, JVM знайде збіг у #1 і не дійде до #2, хоча FileNotFoundException також є IOException.
finally після всіх catch
try {
// код
} catch (IOException e) {
// обробник IO
} catch (Exception e) {
// обробник решти
} finally {
// Виконається після будь-якого catch
}
Коли НЕ використовувати кілька catch блоків
- Однакова обробка — якщо всі catch блоки роблять одне й те саме, використовуйте multi-catch (
A | B) — це коротше і читаніше (Java 7+) - Більше 5 catch блоків — ознака порушення SRP, метод робить занадто багато. Розгляньте поділ на кілька методів
- Ловите
Exceptionколи не важлива специфіка — для generic логування одного catch достатньо - У функціональному стилі — замість catch використовуйте
Either/Tryмонади (Vavr library) - Коли достатньо @ControllerAdvice — у Spring REST один глобальний обробник замінює 10 catch блоків
Caveat: Java 7+ multi-catch як альтернатива
Починаючи з Java 7, замість кількох окремих catch блоків з однаковою логікою можна використовувати multi-catch:
// Замість:
catch (IOException e) {
log.error("Error", e);
throw new ServiceException(e);
}
catch (SQLException e) {
log.error("Error", e);
throw new ServiceException(e);
}
// Пишемо:
catch (IOException | SQLException e) {
log.error("Error", e);
throw new ServiceException(e);
}
Multi-catch переважніший, коли обробка ідентична. Окремі catch потрібні тільки коли логіка різниться (наприклад, retry для IO, але не для SQL).
Middle Level
Як JVM шукає обробник
У .class файлі створюється Exception Table:
| from | to | target | type |
|---|---|---|---|
| 0 | 10 | 13 | FileNotFoundException |
| 0 | 10 | 25 | IOException |
| 0 | 10 | 37 | Exception |
Коли відбувається athrow, JVM сканує таблицю зверху вниз до першого збігу.
Антипатерн: дублювання коду
// Погано — дублювання
try { ... }
catch (IOException e) {
log.error("Error", e);
throw new ServiceException(e);
}
catch (SQLException e) {
log.error("Error", e); // Те саме!
throw new ServiceException(e); // Те саме!
}
// Добре — multi-catch
try { ... }
catch (IOException | SQLException e) {
log.error("Error", e);
throw new ServiceException(e);
}
Розділення логіки
Якщо потрібна різна обробка — окремі catch виправдані:
try {
// код
} catch (FileNotFoundException e) {
log.warn("File not found, using default");
return getDefaultData();
} catch (IOException e) {
log.error("IO error", e);
throw new ServiceException(e);
}
Cost of Catch
Сама спроба входу в catch «безкоштовна» до моменту, поки виняток не викинуто. Наявність Exception Table не сповільнює виконання try блоку.
Senior Level
Exception Table Size
Величезна кількість catch блоків може роздути розмір методу за межі 64КБ → помилка компіляції. Це вірна ознака порушення SRP — метод робить занадто багато.
Polymorphism vs Catch
Замість 10 catch блоків іноді краще:
// Альтернатива — обробка через ієрархію
catch (BusinessException e) {
// Обробить усі підтипи: OrderException, PaymentException, тощо.
handleBusinessError(e);
}
Або Pattern Matching (Java 17+):
catch (Exception e) {
if (e instanceof FileNotFoundException fnf) {
// fnf вже приведений до типу
} else if (e instanceof IOException io) {
// io вже приведений до типу
}
}
Catching Error
Можна ловити Error або Throwable, але після OutOfMemoryError стан додатку непередбачуваний. Ловити варто тільки для «останнього подиху» — логування перед падінням.
Діагностика
javap -v MyClass.class— покаже реальну Exception Table- Log Correlation — зберігайте спільні метадані (Trace ID) для всіх типів помилок
- Static Analysis — Sonar попереджає про дублювання у catch блоках
- Метрики — лічіть кожен тип винятку через Micrometer
🎯 Шпаргалка для інтерв’ю
Обов’язково знати:
- Кілька
catchблоків для одногоtry— дозволено і широко використовується - Правило: від приватного до загального —
FileNotFoundExceptionпередIOExceptionпередException - Компілятор перевіряє порядок на етапі компіляції — неправильний порядок = помилка “already been caught”
- JVM шукає обробник “first match wins” — проходить Exception Table зверху вниз до першого збігу
- Наявність Exception Table не сповільнює виконання
tryблоку — перевірка «безкоштовна» до винятку - Якщо обробка ідентична — використовуйте multi-catch (
IOException | SQLException) замість дублювання - Більше 5 catch блоків — ознака порушення SRP, метод робить занадто багато
Часті уточнюючі запитання:
- Чому не можна
ExceptionпередIOException? —Exceptionперехопить все,IOExceptionстане недосяжним кодом - Як JVM шукає обробник? — Проходить Exception Table зверху вниз, перший збіг захоплює управління
- Коли потрібні окремі catch, а не multi-catch? — Коли різна логіка обробки (retry для IO, не для SQL)
- Чи впливає кількість catch на продуктивність? — O(n) пошук у Exception Table, розміщуйте частіші першими
Червоні прапорці (НЕ говорити):
- “Ставлю
catch (Exception e)першим для простоти” — не скомпілюється з більш приватними catch - “Багато catch блоків не впливають на продуктивність
try” — вірно, але впливають на пошук при винятку - “Використовую catch для
Errorв бізнес-логіці” — післяOutOfMemoryErrorстан непередбачуваний - “Дублюю код у кожному catch” — використовуйте multi-catch при ідентичній обробці
Пов’язані теми:
- [[26. Що таке multi-catch (catching multiple exceptions)]] — альтернатива при однаковій обробці
- [[27. В якому порядку розташовувати catch блоки]] — детальне пояснення ordering
- [[28. Чи можна перехопити і викинути виняток заново]] — rethrow з catch блоку
- [[1. У чому різниця між checked та unchecked exceptions]] — які винятки ловити
- [[4. Які винятки потрібно обробляти обов’язково]] — обов’язкові catch