Урок по треку Java 6 мин

Исключения в Java

Checked и unchecked, try-with-resources, finally, подавленные исключения и обработка ошибок.

Про исключения спрашивают иерархию, checked против unchecked, что делает finally в странных случаях и как правильно закрывать ресурсы.

Иерархия

Throwable
├── Error                 OutOfMemoryError, StackOverflowError: не ловим
└── Exception             checked: IOException, SQLException
    └── RuntimeException  unchecked: NullPointerException, IllegalArgumentException
  • Checked (наследники Exception, кроме RuntimeException): компилятор требует поймать или объявить в throws. Это ожидаемые внешние сбои: файл не найден, сеть.
  • Unchecked (RuntimeException и Error): объявлять не обязательно. Обычно это ошибки программиста: null, неверный аргумент, выход за границы.
  • Error сигнализирует о проблемах JVM, восстанавливаться после них обычно нельзя.

Современные фреймворки (Spring, Hibernate) почти всё заворачивают в unchecked: checked-исключения плохо сочетаются с лямбдами и протекают через все слои.

try, catch, finally

try {
    risky();
} catch (FileNotFoundException e) {   // сначала более узкие
    ...
} catch (IOException | SQLException e) { // multi-catch с Java 7
    ...
} finally {
    cleanup();                          // выполняется почти всегда
}

finally выполняется и при нормальном выходе, и при исключении, и при return из try. Не выполнится, если вызван System.exit, JVM убита или поток завис навсегда.

Ловушки finally:

  • return в finally перекрывает return из try и глотает исключение.
  • Исключение из finally заменяет исходное исключение из try.
  • Если try вернул значение примитива, а finally поменял переменную, вернётся старое значение: результат уже вычислен.

try-with-resources

Для всего, что реализует AutoCloseable:

try (var in = new FileInputStream(path);
     var out = new FileOutputStream(copy)) {
    in.transferTo(out);
}

Ресурсы закрываются автоматически в обратном порядке объявления, до блоков catch и finally. Если и тело, и close() бросили исключения, наружу уходит исключение тела, а исключение закрытия прикрепляется к нему через addSuppressed и доступно в getSuppressed(). С finally вручную так не получится: исключение из close() затёрло бы основное.

Свои исключения и хорошие практики

  • Своё исключение наследуют от RuntimeException (или Exception, если вызывающий реально может обработать) и передают причину: new OrderException("...", e). Потерянный cause превращает отладку в гадание.
  • Не ловить Exception или Throwable без нужды, тем более не глотать пустым catch.
  • Не использовать исключения для управления потоком выполнения: создание исключения со стектрейсом дорогое.
  • Логировать или пробрасывать, но не оба сразу: иначе одна ошибка в логе пять раз.
  • InterruptedException не глотать: либо пробросить, либо восстановить флаг Thread.currentThread().interrupt().

Частые ошибки

  • catch (Exception e) {} и молчаливая потеря ошибки.
  • return в finally.
  • Ручное закрытие ресурсов в finally без try-with-resources.
  • throw new RuntimeException(e.getMessage()) вместо передачи e как причины.

Что спросят на собесе

  • Иерархия исключений, чем checked отличается от unchecked и что вы выбираете для своих.
  • Когда не выполнится finally? Что будет при return в try и в finally?
  • Как работает try-with-resources, что такое suppressed-исключения?
  • Можно ли поймать StackOverflowError и стоит ли?
  • Как правильно обрабатывать InterruptedException?

Закрепите за 5 минут

После урока в тренажёре 3-5 проверочных вопросов, дальше модуль уходит в повторения.