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

Чи можна перехопити і викинути виняток заново?

Повторний кидок (rethrow) — перехоплення винятку в catch з метою вчинити дію (логування) і відправка далі:

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

Junior Level

Так, можна!

Повторний кидок (rethrow) — перехоплення винятку в catch з метою вчинити дію (логування) і відправка далі:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class OrderService {
    private static final Logger log = LoggerFactory.getLogger(OrderService.class);

    public void processOrder(Long orderId) {
        try {
            orderRepository.save(orderId);
        } catch (Exception e) {
            log.error("Failed to process order id={}", orderId, e); // Логуємо
            throw e; // Пробрасуємо далі
        }
    }
}

Навіщо це потрібно

  • Логування — записати помилку перед тим, як вона піде далі
  • Метрики — порахувати кількість помилок
  • Очищення — звільнити ресурси перед пробросом
try {
    connection.beginTransaction();
    connection.save(order);
} catch (SQLException e) {
    connection.rollback(); // Відкочуємо транзакцію
    throw e; // Пробрасуємо далі
}

Обгортка при повторному кидку

Часто огортають в інший виняток:

try {
    repository.save(order);
} catch (SQLException e) {
    throw new OrderException("Failed to save order id=" + order.getId(), e); // Новий тип + контекст
}

Коли НЕ використовувати rethrow

  1. Без додавання цінностіcatch (Exception e) { throw e; } марний без логування/метрик
  2. Дублювання логування — якщо виняток вже залогований на цьому рівні, не логуйте повторно при rethrow
  3. Замість обгортання — якщо потрібен новий тип винятку, використовуйте wrapping з cause, а не голий rethrow
  4. У транзакціях без rollback — rethrow unchecked винятку в @Transactional відкотить транзакцію, checked — ні
  5. Багаторазовий rethrow — не ловте/кидайте один і той самий виняток на кожному шарі — достатньо одного рівня

Middle Level

Інструкція athrow

На рівні байт-коду повторний кидок — звичайна інструкція athrow. Вона бере об’єкт винятку зі стека операндів і шукає обробник у Exception Table.

Важливо: при throw e; стек-трейс не змінюється. У ньому буде рядок оригінального виникнення, а не рядок throw e.

Precise Rethrow (Java 7+)

Одна з найкорисніших, але рідко обговорюваних функцій:

public void process() throws IOException, SQLException {
    try {
        if (condition) throw new IOException();
        else throw new SQLException();
    } catch (Exception e) { // Ловимо загального предка
        log.error("Intermediary processing");
        throw e; // Компілятор «знає» — тільки IOException або SQLException
    }
}

До Java 7 довелося б оголосити throws Exception. Тепер компілятор аналізує вміст try.

Rethrow з обгортанням (Exception Chaining)

// Плюс: додаємо контекст поточного шару
throw new MyException("Context: processing order " + orderId, e);

// Мінус: новий об'єкт + новий fillInStackTrace()
// Завжди передавайте оригінальний виняток як cause!

Side Effects у rethrow

У високонавантажених системах rethrow використовується для метрик:

catch (RuntimeException e) {
    metrics.increment("errors.count");
    throw e;
}

Senior Level

Double Traversal

Повторний кидок змушує JVM заново сканувати Exception Table. У глибоких ієрархіях часті rethrow створюють невелике навантаження на CPU.

Checked to Unchecked

Часто checked винятки пробрасуються як unchecked — «розриває» зобов’язання в сигнатурі:

try {
    // checked exception
    Files.readAllLines(path);
} catch (IOException e) {
    throw new RuntimeException(e); // Unchecked — метод більше не оголошує throws
}

Sneaky Throws

Трюк з пробросом checked винятку без оголошення у throws:

@SuppressWarnings("unchecked")
private static <E extends Throwable> void sneakyThrow(Throwable e) throws E {
    throw (E) e;
}

Lombok @SneakyThrows використовує цей самий механізм.

Losing Exceptions

Якщо у блоці catch викликали метод, який сам кинув виняток, і не пробросили оригінал — оригінал втрачений:

catch (Exception e) {
    log.info("Error");        // OK
    doSomethingThatAlsoThrows(); // Новий виняток! Оригінал e втрачений!
    throw e; // Ніколи не виконається
}

fillInStackTrace() — оновлення стеку

Якщо хочете оновити стек-трейс поточним рядком (щоб у логах було видно місце rethrow):

throw (RuntimeException) e.fillInStackTrace();

Робіть це тільки якщо розумієте навіщо — оригінальний стек-трейс буде втрачено.

Діагностика

  • Log Analysis — переконайтеся, що кожен rethrow логується з контекстом
  • Метрики — лічіть rethrow через Micrometer
  • Debugger — в IntelliJ розгорніть вузол cause для перегляду всього ланцюжка
  • javap -c — покаже інструкцію athrow у байт-коді

🎯 Шпаргалка для інтерв’ю

Обов’язково знати:

  • Rethrow — перехоплення винятку в catch з дією (лог, метрики, rollback) і проброс далі через throw e
  • При throw e; стек-трейс не змінюється — показує оригінальне місце виникнення
  • Checked винятки можна пробросити без втрати типізації завдяки Precise Rethrow (Java 7+)
  • Обгортка при rethrow додає контекст: throw new OrderException("msg", e) — завжди передавайте cause
  • Rethrow unchecked в @Transactional відкотить транзакцію, checked — ні
  • Марний rethrow: catch (Exception e) { throw e; } без логування/метрик/очищення
  • e.fillInStackTrace() оновлює стек-трейс — використовуйте усвідомлено, оригінальний стек буде втрачено

Часті уточнюючі запитання:

  • Чи змінюється стек-трейс при throw e? — Ні, показує оригінальне місце помилки
  • Що таке Precise Rethrow? — Java 7+: компілятор аналізує try і зберігає точні типи у throws
  • Коли rethrow марний? — Без логування, метрик, очищення — голий catch { throw e; }
  • Як оновити стек-трейс?e.fillInStackTrace() — але оригінальний стек буде втрачено

Червоні прапорці (НЕ говорити):

  • “При rethrow стек-трейс оновлюється” — ні, тільки при fillInStackTrace()
  • “Rethrow checked винятку не відкочує @Transactional” — залежить від типу: unchecked відкотить, checked — ні
  • “Багаторазовий rethrow на кожному шарі — гарна практика” — достатньо одного рівня
  • “Rethrow без cause втрачає оригінальну помилку” — при throw e без обгортки помилка не втрачається

Пов’язані теми:

  • [[19. Що таке загортання винятків]] — обгортка при rethrow
  • [[21. Що робить ключове слово throws]] — Precise Rethrow зберігає типи
  • [[29. Що таке chaining винятків]] — chaining при обгортці з cause
  • [[22. Чи можна викинути checked виняток з методу без throws]] — sneaky throws альтернатива
  • [[18. Як правильно логовати винятки]] — логування перед rethrow