Питання 26 · Розділ 7

Що таке multi-catch (catching multiple exceptions)?

До Java 7 розробники стикалися з двома проблемами:

Мовні версії: English Russian Ukrainian

Junior Level

Визначення

Multi-catch — можливість Java 7+ перехоплювати кілька типів винятків в одному блоці catch.

Чому multi-catch з’явився саме в Java 7

До Java 7 розробники стикалися з двома проблемами:

  1. Дублювання коду: якщо IOException та SQLException обробляються однаково, доводилося писати два ідентичні catch блоки.
  2. Загальний предок занадто широкий: ловити 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 зберігає точні типи

Недоліки

  • Змінна eeffectively final
  • Не можна розділити логіку для різних типів (для цього — окремі catch)

Коли НЕ використовувати multi-catch

  1. Різна обробка — якщо IOException потребує retry, а SQLException — ні, розділяйте catch. Multi-catch передбачає ідентичну логіку для всіх типів.
  2. Різні HTTP-статусиValidationException → 400, DataAccessException → 500. Різні статуси = різна обробка = окремі catch.
  3. Interrupted + IOExceptionInterruptedException потребує відновлення флага переривання (Thread.currentThread().interrupt()), звичайний IO — ні. Об’єднання призведе до втрати флага.
  4. Більше 3 типівcatch (A | B | C | D e) стає нечитабельним. Якщо потрібно 4+ типи, ймовірно, метод робить занадто багато.
  5. Коли потрібно додати різний контекст"File not found" vs "DB unavailable" потребують різних повідомлень про помилку. У multi-catch повідомлення буде загальним і менш інформативним.
  6. Коли один з типів — 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