Вопрос 1 · Раздел 7

В чём разница между checked и unchecked exceptions?

В Java все исключения наследуются от java.lang.Throwable. Исключения делятся на две категории:

Версии по языкам: English Russian Ukrainian

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 исключения

  1. Бизнес-логика – checked исключения ломают композицию (нужно объявлять throws в каждом методе вверх по стеку)
  2. REST-контроллеры – Spring сам обработает unchecked исключения через @ExceptionHandler
  3. Stream API – лямбды не поддерживают checked исключения (Function<T,R> не объявляет throws)
  4. 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 потому что:

  1. Загрязнение сигнатур — добавление throws требует изменения всех методов вверх по стеку
  2. Несовместимость с 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]]