Вопрос 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

Связанные темы:

  • [[24. Можно ли несколько catch блоков для одного try]] — раздельные catch для разной обработки
  • [[26. В каком порядке располагать catch блоки]] — ordering не важен внутри multi-catch
  • [[9. Что такое try-with-resources]] — появился в той же Java 7
  • [[20. Что делает ключевое слово throws]] — Precise Rethrow сохраняет точные типы
  • [[3. Что такое unchecked exception (Runtime Exception)]] — RuntimeException не рекомендуется для multi-catch