В чому різниця між checked та unchecked винятками?
В Java усі винятки успадковуються від java.lang.Throwable. Винятки поділяються на дві категорії:
Junior Level
Основи
В Java усі винятки успадковуються від java.lang.Throwable. Винятки поділяються на дві категорії:
Checked exceptions (перевірювані):
- Спадкоємці
Exception(крімRuntimeException) - Компілятор зобов’язує їх обробляти: або через
try-catch, або черезthrowsу сигнатурі метода - Приклади:
IOException,SQLException,ClassNotFoundException
Unchecked exceptions (неперевірювані):
- Спадкоємці
RuntimeException - Компілятор не вимагає їх обробки
- Приклади:
NullPointerException,IllegalArgumentException,IndexOutOfBoundsException
Коли використовувати
- Checked — для зовнішніх відновлюваних збоїв (файл не знайдено, мережа недоступна)
- Unchecked — для помилок програмування (null, невірний аргумент) та бізнес-помилок
// Checked — потрібно обробити або оголосити throws
public void readFile() throws IOException {
Files.readAllLines(Paths.get("file.txt"));
}
// Unchecked — компілятор не вимагає обробки
public void divide(int a, int b) {
if (b == 0) throw new ArithmeticException("Division by zero");
}
Middle Level
Коли НЕ використовувати checked винятки
- Бізнес-логіка – checked винятки ламають композицію (потрібно оголошувати throws у кожному методі вгору по стеку)
- REST-контролери – Spring сам обробить unchecked винятки через @ExceptionHandler
- Stream API – лямбди не підтримують checked винятки (Function<T,R> не оголошує throws)
- CompletableFuture – винятки загортаються в CompletionException, checked втрачають сенс
Механізм роботи
JVM не примусово перевіряє типи винятків у рантаймі – розділення checked/unchecked працює лише на етапі компіляції. У байткоді усі винятки обробляються однаково. Інструкція athrow просто викидає об’єкт.
Exception Translation
Гарна практика – перехоплювати checked винятки на межі шарів і загортати їх у доменні unchecked винятки:
public class UserRepository {
public User findById(Long id) {
try {
return jdbcTemplate.queryForObject("SELECT * FROM users WHERE id = ?",
(rs, rowNum) -> new User(rs.getLong("id"), rs.getString("name")), id);
} catch (DataAccessException e) {
throw new UserNotFoundException("User not found: " + id, e);
}
}
}
Multi-catch (Java 7+)
try {
// код
} catch (IOException | SQLException e) {
log.error("Resource error", e);
throw new ServiceException("Failed", e);
}
Чому сучасні мови відмовилися від checked exceptions
Kotlin і Scala не мають checked exceptions тому що:
- Забруднення сигнатур — додавання
throwsвимагає зміни всіх методів вгору по стеку - Несумісність з FP — функціональні інтерфейси (Stream API, CompletableFuture) не підтримують checked винятки
Senior Level
Under the Hood: байт-код та JVM
Атрибут Exceptions у .class файлі зберігає інформацію про checked винятки, але JVM використовує його лише для верифікації, а не для контролю в рантаймі.
Sneaky Throws — обхід компілятора
Оскільки JVM не перевіряє типи винятків, можна кинути checked виняток без throws:
@SuppressWarnings("unchecked")
private static <E extends Throwable> void sneakyThrow(Throwable e) throws E {
throw (E) e;
}
// Використання: sneakyThrow(new IOException()); — компілюється без throws!
Lombok @SneakyThrows використовує той самий механізм.
Стирання типів: дженерик E стирається до Throwable у рантаймі. Компілятор думає, що метод кидає E (параметр метода), а JVM викидає реальний тип. Тому throws не потрібен у сигнатурі.
⚠️ Це небезпечно: викликач не знає, що метод може кинути IOException. Використовуйте лише всередині фреймворків, ніколи в бізнес-логіці.
Продуктивність у Highload
fillInStackTrace() — найдорожча частина винятку. JVM проходить по всьому стеку викликів. При 100k+ RPS створення винятків стає bottleneck.
Оптимізація: для винятків-сигналів вимкніть стек-трейс:
public class FastException extends RuntimeException {
public FastException(String message) {
super(message, null, false, false); // writableStackTrace = false
}
}
Це прискорює створення у 10-50 разів.
Архітектурний вибір
Checked винятки хороші лише для зовнішніх відновлюваних збоїв у бібліотеках низького рівня:
InterruptedException— Java змушує обробляти переривання, щоб не зламати управління потоками
Unchecked винятки переважні тому що:
- Чистота бізнес-логіки без
try-catchшуму - Централізована обробка через
@ControllerAdvice - Сумісність з Reactive programming
Edge Cases
- Exception Translation на межі шарів — DAO перехоплює
SQLExceptionі загортає у доменний unchecked виняток - UncaughtExceptionHandler — завжди встановлюйте глобальний обробник для потоків
- Stack Trace Filtering — у Highload обрізайте стек-трейс або використовуйте JSON формат (Logstash)
🎯 Шпаргалка для співбесіди
Обов’язково знати:
- Checked exceptions — спадкоємці
Exception(крімRuntimeException), компілятор зобов’язує обробити - Unchecked exceptions — спадкоємці
RuntimeException, компілятор не вимагає обробки - Checked — для зовнішніх відновлюваних збоїв (файл, мережа, БД)
- Unchecked — для помилок програмування та бізнес-помилок
- JVM не перевіряє типи винятків у рантаймі — розділення лише на рівні компілятора
- Kotlin і Scala відмовилися від checked через забруднення сигнатур та несумісність з FP
sneakyThrowдозволяє обійти перевірку компілятора через стирання типів- Для highload вимикайте стек-трейс (
writableStackTrace = false)
Часті уточнюючі запитання:
- Чому сучасні мови відмовилися від checked exceptions? — Забруднення сигнатур, несумісність з лямбдами та Stream API
- Що таке exception translation? — Перехоплення checked винятку на межі шару і загортання у доменний unchecked
- Як працює sneakyThrow? — Дженерик
E extends Throwableстирається доThrowable, JVM не перевіряє тип - Коли checked виняток виправданий? — Для зовнішніх відновлюваних збоїв у бібліотеках низького рівня (IOException, InterruptedException)
Червоні прапорці (НЕ говорити):
- “Я завжди використовую checked exceptions, щоб змусити колег обробляти помилки” — це антипатерн для бізнес-логіки
- “JVM перевіряє checked exceptions у рантаймі” — ні, лише компілятор
- “Kotlin не підтримує винятки взагалі” — Kotlin підтримує unchecked, просто прибрав checked
Пов’язані теми:
- [[Що таке checked виняток і коли його використовувати]]
- [[Що таке unchecked виняток (Runtime Exception)]]
- [[Які винятки потрібно обробляти обов’язково]]
- [[В чому різниця між Error та Exception]]
- [[Що таке Throwable]]