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

В якому порядку потрібно розташовувати catch блоки?

Блоки catch мають йти від більш конкретних до більш загальних:

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

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:

  1. Шукає перший запис, де тип збігається з винятком (або його супертипом).
  2. Переходить на target — адрес відповідного catch блоку.
  3. Решта записів ігноруються.

Тому “first match” — це буквально перший запис, що збігся, в Exception Table.

Просте правило

Розташовуйте за ієрархією наслідування — від нащадків до предків.

Коли НЕ слідувати правилу «від приватного до загального»

  1. Ніколи не порушуйте — це правило enforced компілятором, зворотний порядок не скомпілюється
  2. Multi-catch всередині — порядок типів у catch (A | B e) не має значення, бо обидва типи вказують на один target у Exception Table
  3. Не ловіть Throwable в бізнес-логіці — тільки на межі системи
  4. Не ставте рідкісні винятки перед частими — якщо FileNotFoundException трапляється у 100x частіше за EOFException, ловіть його першим для мікро-оптимізації пошуку в Exception Table
  5. Не мішайте рівні абстракції — не чергуйте бізнес- та інфраструктурні винятки без логіки

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 блоків НЕ важливий

  1. Усі catch блоки обробляють однаково — якщо кожен catch робить log.error(e); throw new ServiceException(e);, порядок не впливає на результат. Проте в цьому випадку краще використовувати multi-catch.

  2. Винятки не пов’язані наслідуваннямcatch (SQLException e) та catch (NullPointerException e) не мають спільного підтипу. Вони ніколи не перетнуться, і порядок між ними не впливає на коректність (хоча може впливати на продуктивність пошуку).

  3. Multi-catch — всередині catch (A | B e) порядок типів не має значення. Компілятор генерує окремі записи в Exception Table, але всі вони вказують на один target.

  4. Єдиний 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)]] — неправильна обробка