Про исключения спрашивают иерархию, 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?