Перейти к содержанию

Java Exceptions

Исключения Java описывают сбои, которые прерывают обычный поток выполнения. Код может обработать их на месте, преобразовать на границе абстракции или передать выше по стеку вызовов.

Иерархия исключений

Throwable, Error и Exception

Выбросить или перехватить можно только экземпляр Throwable. Две его основные ветки - Error и Exception.

Error обычно сообщает о серьёзном состоянии JVM или среды выполнения, например OutOfMemoryError или StackOverflowError. Прикладной код обычно не перехватывает Error для восстановления. Поэтому перехват Throwable чаще всего слишком широк и может также нарушить механизмы отмены или завершения работы.

Exception представляет сбои, с которыми приложение потенциально может справиться. RuntimeException - его непроверяемая ветка, включающая ошибки программирования и нарушения контрактов: NullPointerException, IllegalArgumentException и IndexOutOfBoundsException.

Проверяемые и непроверяемые исключения

Проверяемое исключение - любой подтип Throwable, который не является подтипом RuntimeException или Error. Java требует перехватить его либо объявить через throws.

Для непроверяемых исключений (RuntimeException и Error) такого требования компилятора нет. Это не делает их безобидными: перехватывайте исключение только там, где код может восстановиться, добавить полезный контекст, преобразовать ошибку или сообщить о ней на границе системы.

В Kotlin нет проверяемых исключений на уровне языка. Компилятор не требует перехватывать исключения, объявленные Java API, поэтому Kotlin-код всё равно должен учитывать их контракты. Используйте @Throws, если Kotlin-функция должна объявить исключения для вызывающего Java-кода, в том числе Java-фреймворков.

Обработка исключений

try, catch, finally, throw и throws

try содержит код, который может завершиться ошибкой. catch обрабатывает подходящие типы; более конкретные обработчики должны идти раньше общих, иначе последующий подтип станет недостижимым. Multi-catch объединяет альтернативы с одинаковой обработкой:

try {
    repository.load();
} catch (IOException | SecurityException exception) {
    logger.log(exception);
    throw new DataLoadException("Unable to load data", exception);
}

throw выбрасывает один экземпляр исключения. throws объявляет возможные проверяемые исключения в сигнатуре метода и переносит обязанность обработки на вызывающий код. Переопределяющий метод не может добавлять более общие проверяемые исключения, чем допускает исходный метод.

finally обычно выполняется, даже если try завершился возвратом или исключением, поэтому подходит для освобождения ресурсов, которые не реализуют AutoCloseable. Абсолютной гарантии нет: завершение процесса или JVM может помешать выполнению. Избегайте return и throw внутри finally, потому что они могут скрыть исходный результат или исключение.

Try-with-resources

Для ресурсов AutoCloseable предпочитайте try-with-resources. Ресурсы закрываются автоматически в порядке, обратном объявлению:

try (InputStream input = files.open()) {
    return input.readAllBytes();
}

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

Сохраняйте контекст и причину

Не используйте пустой catch, не перехватывайте общие исключения вокруг несвязанной работы и не логируйте одно исключение повторно на каждом уровне перед пробросом. Такие приёмы скрывают сбои или создают дублирующиеся записи.

Преобразуя низкоуровневое исключение, сохраняйте исходную причину:

throw new DataLoadException("Unable to read user profile", exception);

Добавляйте контекст, помогающий определить операцию, но не записывайте в журнал секреты, токены, персональные данные и полные тела ответов. Исключения должны описывать нештатные сбои, а не обычные ветки вроде ожидаемого пустого результата поиска.

В Android неперехваченное исключение в главном потоке обычно завершает процесс приложения. Обрабатывайте ожидаемые ошибки на подходящей границе, при необходимости преобразуйте их в состояние UI и сохраняйте исходную причину для диагностики.

Связанные темы

Источники