Які винятки потрібно обробляти обов'язково?
Компілятор Java змушує обробляти checked винятки. Це усі спадкоємці Exception, крім RuntimeException.
Junior Level
Checked винятки — обов’язкова обробка
Компілятор Java змушує обробляти checked винятки. Це усі спадкоємці Exception, крім RuntimeException.
Checked exceptions – це контракт між методом і його викликачем. Метод каже: «Я можу зіткнутися з X, і ти повинен бути до цього готовий.»
// Обов'язково: або try-catch, або throws
public void readFile() throws IOException {
Files.readAllLines(Paths.get("file.txt"));
}
// Або обробка на місці
public void safeRead() {
try {
Files.readAllLines(Paths.get("file.txt"));
} catch (IOException e) {
log.error("Failed to read file", e);
}
}
Поширені винятки для обробки
IOException— робота з файлами, мережеюSQLException— робота з базою данихClassNotFoundException— завантаження класівInterruptedException— багатопоточність
Unchecked винятки — необов’язкова обробка
NullPointerException, IllegalArgumentException та інші RuntimeException можна не обробляти. Вони сигналізують про помилки в коді.
Базове правило
- Checked — зовнішні збої (файли, мережа, БД) → обов’язково обробити
- Unchecked — помилки програмування → повинні виправити код
Middle Level
Обов’язкова vs рекомендована обробка
| Тип | Обов’язок | Хто вимагає |
|---|---|---|
| Checked | Обов’язок обробити або оголосити throws | Компілятор |
| Unchecked | Рекомендується обробити | Архітектура/команда |
| Error | Не повинен перехоплювати | JVM (відновлення неможливе) |
Компіляторна перевірка
На рівні JVM розділення на checked та unchecked не існує. Інструкція athrow просто викидає об’єкт. Обов’язковість обробки — це лише перевірка javac.
Атрибут Exceptions у class-файлі
При оголошенні void method() throws IOException у .class файлі з’являється атрибут Exceptions. Компілятор використовує його для перевірки. Інші мови (Kotlin, Groovy) ігнорують цей атрибут.
Sneaky Throws — обхід перевірки
Можна кинути checked виняток без throws:
@SuppressWarnings("unchecked")
public static <E extends Throwable> void sneakyThrow(Throwable e) throws E {
throw (E) e; // Стирання типів обманює компілятор
}
// Lombok робить це автоматично
@SneakyThrows
public void readFile() {
// IOException без throws!
Files.readAllLines(Paths.get("file.txt"));
}
Exception Translation
Правильний підхід — перехоплювати checked винятки на межі шару і перекладати у доменні unchecked:
public interface Storage {
byte[] read(String path); // Не залежить від SQL
}
public class SqlStorage implements Storage {
public byte[] read(String path) {
try {
return jdbcTemplate.queryForObject(...);
} catch (SQLException e) {
throw new StorageException("Failed to read: " + path, e);
}
}
}
Senior Level
Функціональне програмування та checked exceptions
Стандартні інтерфейси (Function, Predicate, Supplier) не підтримують checked винятки:
// Не скомпілюється
stream.map(path -> Files.readString(path))
// Рішення 1 — обгортка
stream.map(path -> {
try { return Files.readString(path); }
catch (IOException e) { throw new UncheckedIOException(e); }
})
// Рішення 2 — Sneaky Throws
stream.map(path -> sneakyRead(path))
Продуктивність
Ніколи не використовуйте checked винятки для управління логікою. Кидання винятку — створення об’єкта + обхід стека — у 100-1000 разів повільніше повернення Optional.
Валідація вхідних даних
Для невалідних даних завжди використовуйте unchecked винятки (IllegalArgumentException). Це помилка програміста, а не зовнішнього світу.
InterruptedException – виняток, що підтверджує правило. Це не «control flow» у звичайному сенсі, а механізм кооперативної відміни потоків. Його обробка – частина контракту багатопоточності.
UncaughtExceptionHandler
Якщо checked виняток “просочився” через sneaky throws і не був спійманий, він дійде до обробника потоку. Встановіть глобальний обробник:
Thread.setDefaultUncaughtExceptionHandler((thread, e) -> {
log.error("Uncaught exception in thread {}", thread.getName(), e);
});
Empty Catch Blocks — найгірший злочин
catch (IOException e) {} // Злочин!
Якщо повинні обробити, але не знаєте як — логуйте:
catch (IOException e) {
log.error("IO error occurred", e);
throw new RuntimeException(e);
}
Діагностика
- Static Analysis — SonarQube блокує порожні catch-блоки
- Micrometer — відстежуйте кількість винятків через метрики
- Log Correlation — зберігайте Trace ID для всіх типів помилок
🎯 Шпаргалка для співбесіди
Обов’язково знати:
- Checked винятки — обов’язково обробити або оголосити
throws(компілятор вимагає) - Unchecked — рекомендується обробити на рівні архітектури (не компілятор)
- Error — НЕ повинен перехоплюватися (JVM у нестабільному стані)
- На рівні JVM розділення немає — інструкція
athrowвикидає все однаково - Sneaky throws — обхід перевірки компілятора через стирання типів
- Exception translation — перехоплення checked на межі шару → переклад у доменний unchecked
- Для невалідних вхідних даних завжди використовувати unchecked (
IllegalArgumentException) - Порожні catch-блоки — найгірший злочин: мінімум логувати
Часті уточнюючі запитання:
- Як JVM обробляє checked vs unchecked? — Одинаково; розділення лише у
javac - Що таке sneaky throws? — Обхід перевірки через
@SneakyThrows(Lombok) або дженерик-стирання - Чому InterruptedException — особливий? — Це механізм кооперативної відміни потоків, потрібно відновити статус переривання
- Що робити, якщо не знаєш як обробити? — Логувати і пробросити як
RuntimeException
Червоні прапорці (НЕ говорити):
- “Я залишаю порожній catch-блок, якщо не знаю що робити” — хоча б логуйте
- “Я ловлю Error, щоб додаток не падав” — JVM нестабільна, це безглуздо
- “Checked винятки — це те саме, що unchecked” — ні, компілятор перевіряє лише checked
Пов’язані теми:
- [[Що таке checked виняток і коли його використовувати]]
- [[Що таке unchecked виняток (Runtime Exception)]]
- [[В чому різниця між Error та Exception]]
- [[Чому не варто ковтати винятки (catch empty)]]
- [[Що таке suppressed exceptions]]