В якому порядку потрібно розташовувати catch блоки?
Блоки catch мають йти від більш конкретних до більш загальних:
Junior Level
Головне правило: від приватного до загального
Блоки catch мають йти від більш конкретних до більш загальних:
try {
Files.readAllLines(Paths.get("file.txt"));
} catch (FileNotFoundException e) {
// 1. Спочатку приватний випадок
System.out.println("File not found");
} catch (IOException e) {
// 2. Потім більш загальний
System.out.println("IO error");
} catch (Exception e) {
// 3. Найзагальніший — в кінці
System.out.println("Unknown error");
}
Чому так
Якщо поставити загальний catch першим, він перехопить все, і приватні catch ніколи не виконаються:
// Помилка компіляції!
try {
// код
} catch (Exception e) {
// Перехопить все, включаючи IOException
} catch (IOException e) {
// Ніколи не виконається — код недосяжний
}
Компілятор видасть: "exception IOException has already been caught".
Механізм “first match wins” на рівні компілятора
На етапі компіляції:
Компілятор javac перевіряє кожен catch блок послідовно зверху вниз (JLS 14.20). Для кожного нового catch він ставить запитання: «Чи є тип цього catch підтипом будь-якого з catch, які вже були вище?»
- Якщо так — компілятор видає помилку, бо код у цьому catch блоці недосяжний (unreachable code, JLS 14.21).
- Якщо ні — catch додається до Exception Table класу.
Це статична перевірка — компілятор не допускає компіляції з неправильним порядком. Тому у правильно скомпільованому коді порядок завжди коректний.
На етапі виконання (JVM):
Exception Table — це масив записів {from, to, target, type}. При викиданні винятку JVM:
- Шукає перший запис, де тип збігається з винятком (або його супертипом).
- Переходить на
target— адрес відповідного catch блоку. - Решта записів ігноруються.
Тому “first match” — це буквально перший запис, що збігся, в Exception Table.
Просте правило
Розташовуйте за ієрархією наслідування — від нащадків до предків.
Коли НЕ слідувати правилу «від приватного до загального»
- Ніколи не порушуйте — це правило enforced компілятором, зворотний порядок не скомпілюється
- Multi-catch всередині — порядок типів у
catch (A | B e)не має значення, бо обидва типи вказують на один target у Exception Table - Не ловіть
Throwableв бізнес-логіці — тільки на межі системи - Не ставте рідкісні винятки перед частими — якщо
FileNotFoundExceptionтрапляється у 100x частіше заEOFException, ловіть його першим для мікро-оптимізації пошуку в Exception Table - Не мішайте рівні абстракції — не чергуйте бізнес- та інфраструктурні винятки без логіки
Caveat: Java 7+ multi-catch змінює підхід до ordering
З появою multi-catch у Java 7, питання порядку стає менш критичним у деяких сценаріях. Якщо у вас є 3+ винятки з однаковою обробкою, замість:
// 3 catch блоки — порядок важливий (FileNotFoundException перед IOException)
catch (FileNotFoundException e) { handle(); }
catch (SQLException e) { handle(); }
catch (IOException e) { handle(); }
Можна написати:
// 1 catch блок — порядок всередині multi-catch НЕ важливий
catch (FileNotFoundException | SQLException | IOException e) { handle(); }
Multi-catch знімає проблему ordering, бо всі типи обробляються однаково і вказують на один target в Exception Table.
Коли порядок catch блоків НЕ важливий
-
Усі catch блоки обробляють однаково — якщо кожен catch робить
log.error(e); throw new ServiceException(e);, порядок не впливає на результат. Проте в цьому випадку краще використовувати multi-catch. -
Винятки не пов’язані наслідуванням —
catch (SQLException e)таcatch (NullPointerException e)не мають спільного підтипу. Вони ніколи не перетнуться, і порядок між ними не впливає на коректність (хоча може впливати на продуктивність пошуку). -
Multi-catch — всередині
catch (A | B e)порядок типів не має значення. Компілятор генерує окремі записи в Exception Table, але всі вони вказують на один target. -
Єдиний catch блок —
catch (Exception e)— нічого впорядковувати.
Middle Level
First Match Wins
JVM проходить Exception Table зверху вниз. Перший збіг захоплює управління.
Стратегія розташування
1. Очікувані vs Виняткові:
Найчастіші бізнес-винятки (наприклад, ValidationException) ловіть першими — вони будуть на початку Exception Table.
2. Різна обробка:
@PostMapping("/orders")
public ResponseEntity<?> createOrder(@RequestBody OrderRequest req) {
try {
return ResponseEntity.ok(orderService.create(req));
} catch (ValidationException e) {
// 400 Bad Request — помилка клієнта
return ResponseEntity.badRequest().body(e.getMessage());
} catch (ResourceNotFoundException e) {
// 404 Not Found
return ResponseEntity.notFound().build();
} catch (DataAccessException e) {
// 500 Server Error — наша проблема
log.error("Database error processing order", e);
return ResponseEntity.internalServerError().build();
}
}
Multi-catch порядок
У catch (A | B e) порядок всередині блоку не має значення:
catch (IOException | SQLException e) { }
catch (SQLException | IOException e) { }
// Одне й те саме
Але все одно не можна об’єднувати батька і нащадка.
Взаємодія з try-with-resources
Винятки при автоматичному закритті ресурсів (close()) виникають до потрапляння у ваші catch блоки. Ваш catch може спіймати виняток з close(), якщо в основному try помилок не було.
Senior Level
Exception Table та продуктивність
Пошук у Exception Table — складність O(n). У методах з десятками catch блоків це може мати мікроскопічний вплив на продуктивність. Розміщуйте найчастіші винятки першими.
Обробка Error
Якщо ловите Throwable, ви перехоплюєте і Error (напр. OutOfMemoryError):
// Правило: специфічні Exception вище Throwable
try { ... }
catch (BusinessException e) { } // Бізнес-помилки
catch (DataAccessException e) { } // Інфраструктура
catch (Throwable t) { } // Тільки в кінці, на межі системи
Ніколи не робіть catch (Throwable t) посередині бізнес-логіки — тільки на самих зовнішніх межах системи.
Exception Hiding
Неправильний порядок може «сховати» важливу помилку в загальному лозі. Моніторинг не побачить різниці між ValidationException та OutOfMemoryError.
Static Analysis
Використовуйте Checkstyle або Sonar для гарантії одноманітного підходу до ієрархії перехоплення у всьому проєкті.
Діагностика
javap -c— покаже Exception Table з пріоритетами стрибків на міткиcatch- Метрики — лічіть кожен тип винятку окремо
- Log Correlation — зберігайте Trace ID для всіх типів помилок
- Global Error Handler —
@ControllerAdviceловить все, що не спіймали локально
🎯 Шпаргалка для інтерв’ю
Обов’язково знати:
- Головне правило: від приватного до загального — нащадки перед предками в Exception Table
- Компілятор не допустить зворотний порядок — помилка “exception X has already been caught”
- JVM проходить Exception Table зверху вниз, перший збіг захоплює управління (first match wins)
- Порядок всередині
catch (A | B e)НЕ важливий — всі типи → один target - Винятки, не пов’язані наслідуванням, можна розташовувати в будь-якому порядку між собою
- Розміщуйте найчастіші винятки першими для мікро-оптимізації пошуку
catch (Throwable t)— тільки на межі системи, ніколи в бізнес-логіці
Часті уточнюючі запитання:
- Що буде якщо переплутати порядок? — Помилка компіляції, код не скомпілюється
- Чи впливає порядок на продуктивність? — O(n) пошук, часті винятки першими
- Коли порядок НЕ важливий? — Multi-catch, не пов’язані наслідуванням типи, єдиний catch
- Чому
Throwableловимо тільки в кінці? — ПерехопитьErrorвключаючиOutOfMemoryError
Червоні прапорці (НЕ говорити):
- “Ставлю загальний catch першим для зручності” — не скомпілюється
- “Порядок у multi-catch важливий” — ні, всі типи → один target
- “Ловлю
Throwableпосередині бізнес-логіки” — перехопитьError, стан непередбачуваний - “Порядок catch впливає на коректність скомпільованого коду” — компілятор гарантує коректний порядок
Пов’язані теми:
- [[25. Чи можна мати кілька catch блоків для одного try]] — кілька catch та first match wins
- [[26. Що таке multi-catch (catching multiple exceptions)]] — multi-catch знімає проблему ordering
- [[1. У чому різниця між checked та unchecked exceptions]] — ієрархія винятків
- [[5. Що знаходиться на вершині ієрархії винятків]] — Throwable та Error
- [[20. Чому не варто ковтати винятки (порожній catch)]] — неправильна обробка