В чём разница между checked и unchecked exceptions?
В 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 exceptions хороши только для внешних восстановимых сбоев в библиотеках низкого уровня:
InterruptedException— Java заставляет обрабатывать прерывание, чтобы не сломать управление потоками
Unchecked exceptions предпочтительны потому что:
- Чистота бизнес-логики без
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 exception оправдан? — Для внешних восстановимых сбоев в библиотеках низкого уровня (IOException, InterruptedException)
Красные флаги (НЕ говорить):
- “Я всегда использую checked exceptions, чтобы заставить коллег обрабатывать ошибки” — это антипаттерн для бизнес-логики
- “JVM проверяет checked exceptions в рантайме” — нет, только компилятор
- “Kotlin не поддерживает исключения вообще” — Kotlin поддерживает unchecked, просто убрал checked
Связанные темы:
- [[Что такое checked exception и когда его использовать]]
- [[Что такое unchecked exception (Runtime Exception)]]
- [[Какие исключения нужно обрабатывать обязательно]]
- [[В чём разница между Error и Exception]]
- [[Что такое Throwable]]