Що таке multi-catch (catching multiple exceptions)?
До Java 7 розробники стикалися з двома проблемами:
Junior Level
Визначення
Multi-catch — можливість Java 7+ перехоплювати кілька типів винятків в одному блоці catch.
Чому multi-catch з’явився саме в Java 7
До Java 7 розробники стикалися з двома проблемами:
- Дублювання коду: якщо
IOExceptionтаSQLExceptionобробляються однаково, доводилося писати два ідентичні catch блоки. - Загальний предок занадто широкий: ловити
Exception— занадто грубо, втрачається типізація при пробросі (throws Exception).
У Java 7 проєкт Coin (Small Language Changes) додав multi-catch як рішення цих проблем. Паралельно з’явилися try-with-resources та Precise Rethrow — усі три фічі працювали разом для покращення обробки винятків.
import java.io.IOException;
import java.sql.SQLException;
public class DataService {
public void loadData() {
try {
// IO + DB операції
} catch (IOException | SQLException e) {
// Один блок для обох винятків
// e.getClass() поверне реальний тип: IOException або SQLException
throw new DataException("Resource operation failed", e);
}
}
}
Синтаксис
Типи розділяються вертикальною рисою |:
catch (ExceptionType1 | ExceptionType2 | ExceptionType3 e) {
// Спільна обробка
}
Обмеження: типи в multi-catch не повинні бути у відношенні наслідування
Компілятор забороняє вказувати типи, пов’язані наслідуванням:
// Помилка компіляції — FileNotFoundException наслідує IOException
catch (FileNotFoundException | IOException e) { }
ЧОМУ це обмеження: якби компілятор дозволив FileNotFoundException | IOException, виникла б неоднозначність. FileNotFoundException вже є IOException, тому multi-catch з обома типами надлишковий — IOException покриває обидва випадки. Компілятор вважає це помилкою, щоб запобігти плутанині.
Як компілятор перевіряє: при компіляції multi-catch компілятор будує попарну перевірку: для кожної пари типів (A, B) у multi-catch він перевіряє, що A не є підтипом B і B не є підтипом A. Якщо порушено — помилка "Alternatives in a multi-catch statement cannot be related by subclassing".
Як виправити: приберіть підтип з multi-catch:
// Правильно — тільки батько покриває всі випадки
catch (IOException e) { }
// Або окремі catch, якщо потрібна різна обробка:
catch (FileNotFoundException e) { /* специфіка */ }
catch (IOException e) { /* решта */ }
Змінна винятку
У multi-catch змінна `e** неявно фінальна** — не можна перепризначити:
catch (IOException | SQLException e) {
e = new Exception(); // Помилка компіляції!
}
Middle Level
Precise Rethrow (Точний проброс)
Критично важлива особливість Java 7+:
public void process() throws IOException, SQLException { // Точні типи!
try {
// код, який кидає IOException або SQLException
} catch (Exception e) { // Ловимо загального предка
log.error("Error occurred");
throw e; // Компілятор розуміє, що полетить тільки IOException або SQLException
}
}
До Java 7 довелося б оголосити throws Exception. Тепер компілятор аналізує вміст try і дозволяє пробрасяти без втрати типізації.
Байт-код
Multi-catch не створює один загальний обробник. У Exception Table створюються окремі записи для кожного типу, що вказують на один target:
| from | to | target | type |
|---|---|---|---|
| 0 | 10 | 15 | IOException |
| 0 | 10 | 15 | SQLException |
Переваги
- Менше коду — не дублюється обробка
- Менше .class файл — байт-код не дублюється
- Чистіша сигнатура — Precise Rethrow зберігає точні типи
Недоліки
- Змінна
e—effectively final - Не можна розділити логіку для різних типів (для цього — окремі
catch)
Коли НЕ використовувати multi-catch
- Різна обробка — якщо
IOExceptionпотребує retry, аSQLException— ні, розділяйте catch. Multi-catch передбачає ідентичну логіку для всіх типів. - Різні HTTP-статуси —
ValidationException→ 400,DataAccessException→ 500. Різні статуси = різна обробка = окремі catch. - Interrupted + IOException —
InterruptedExceptionпотребує відновлення флага переривання (Thread.currentThread().interrupt()), звичайний IO — ні. Об’єднання призведе до втрати флага. - Більше 3 типів —
catch (A | B | C | D e)стає нечитабельним. Якщо потрібно 4+ типи, ймовірно, метод робить занадто багато. - Коли потрібно додати різний контекст —
"File not found"vs"DB unavailable"потребують різних повідомлень про помилку. У multi-catch повідомлення буде загальним і менш інформативним. - Коли один з типів — RuntimeException — unchecked винятки зазвичай не ловлять явно. Їх наявність у multi-catch сигналізує про дизайн, який варто переглянути.
Senior Level
Reflection та multi-catch
Чистий код при обробці винятків рефлексії:
try {
Method method = MyClass.class.getDeclaredMethod("doSomething");
method.invoke(obj);
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
log.error("Reflection failed", e);
throw new FrameworkException("Method invocation failed", e);
}
InterruptedException у multi-catch
Якщо об’єднуєте InterruptedException з іншими, не забудьте відновити статус переривання:
catch (InterruptedException | IOException e) {
if (e instanceof InterruptedException) {
Thread.currentThread().interrupt();
}
log.error("Interrupted or IO error", e);
}
Code Size та Maintenance
Multi-catch зменшує розмір .class файлу і спрощує підтримку у високонавантажених системах, де логування помилок стандартизоване.
Precise Logging
e.getClass().getName() поверне ім’я реального викинутого класу, а не загального предка:
catch (IOException | SQLException e) {
log.error("{} occurred", e.getClass().getSimpleName(), e);
// Виведе "FileNotFoundException" або "SQLTimeoutException"
}
Діагностика
- SonarLint — підказує замінити однакові
catchблоки на multi-catch javap -v— покаже окремі записи Exception Table для кожного типу- Метрики —
e.getClass().getSimpleName()для категоризації в Prometheus
🎯 Шпаргалка для інтерв’ю
Обов’язково знати:
- Multi-catch (Java 7+) — перехоплення кількох типів винятків в одному
catchчерез| - З’явився у проєкті Coin (Java 7) разом з try-with-resources та Precise Rethrow
- Типи в multi-catch не повинні бути у відношенні наслідування —
FileNotFoundException | IOException= помилка компіляції - Змінна
eу multi-catch неявно фінальна — не можна перепризначити - У Exception Table створюються окремі записи для кожного типу, що вказують на один target
- Precise Rethrow:
catch (Exception e) { throw e; }зберігає точні типи уthrows(Java 7+)
Часті уточнюючі запитання:
- Чому не можна
FileNotFoundException | IOException? —FileNotFoundExceptionвже єIOException, неоднозначність - Що таке Precise Rethrow? — Компілятор аналізує
tryі розуміє точні типи приthrow eзcatch (Exception e) - Коли НЕ використовувати multi-catch? — Різна обробка, різні HTTP-статуси,
InterruptedException(потребує відновлення флага) - Як дізнатися реальний тип у multi-catch? —
e.getClass()поверне фактичний тип, не загального предка
Червоні прапорці (НЕ говорити):
- “Об’єдную
InterruptedExceptionз іншими без відновлення флага” — втрата переривання потоку - “Multi-catch створює один загальний обробник у байт-коді” — ні, окремі записи для кожного типу
- “Пишу
throws Exceptionз multi-catch” — Precise Rethrow дозволяє точні типи - “Multi-catch повільніший за окремі catch” — однаково, та сама Exception Table
Пов’язані теми:
- [[25. Чи можна мати кілька catch блоків для одного try]] — окремі catch для різної обробки
- [[27. В якому порядку розташовувати catch блоки]] — ordering не важливий всередині multi-catch
- [[9. Що таке try-with-resources]] — з’явився в тій самій Java 7
- [[21. Що робить ключове слово throws]] — Precise Rethrow зберігає точні типи
- [[3. Що таке unchecked exception (Runtime Exception)]] — RuntimeException не рекомендується для multi-catch