В каком порядке располагать 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” — это буквально первый совпавший entry в 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 влияет на корректность скомпилированного кода” — компилятор гарантирует корректный порядок
Связанные темы:
- [[24. Можно ли несколько catch блоков для одного try]] — несколько catch и first match wins
- [[25. Что такое multi-catch (catching multiple exceptions)]] — multi-catch снимает проблему ordering
- [[1. В чём разница между checked и unchecked exceptions]] — иерархия исключений
- [[5. Что находится в вершине иерархии исключений]] — Throwable и Error
- [[19. Почему не стоит глотать исключения (catch empty)]] — неправильная обработка