Чи можна перехопити і викинути виняток заново?
Повторний кидок (rethrow) — перехоплення винятку в catch з метою вчинити дію (логування) і відправка далі:
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
- Без додавання цінності —
catch (Exception e) { throw e; }марний без логування/метрик - Дублювання логування — якщо виняток вже залогований на цьому рівні, не логуйте повторно при rethrow
- Замість обгортання — якщо потрібен новий тип винятку, використовуйте wrapping з cause, а не голий rethrow
- У транзакціях без rollback — rethrow unchecked винятку в
@Transactionalвідкотить транзакцію, checked — ні - Багаторазовий 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