В методе ниже обработчик поглощает исключение из try. Нужно ли всё равно объявлять throws IOException для компиляции?
class Demo {
static void run() {
try {
throw new IllegalStateException();
} catch (RuntimeException e) {
System.out.println("handled");
} finally {
throw new java.io.IOException();
}
}
}
Да, метод run обязан объявить throws IOException либо перехватить это исключение внутри finally. Обработка исключения из try не распространяется на исключение, которое выбрасывается самим finally.
В Java проверяемые исключения должны быть видны в контракте метода, чтобы вызывающий код мог заранее учесть возможный путь завершения. Для этого компилятор анализирует не только тело try, но и все конструкции, которые могут завершить метод исключением.
Блок finally предназначен для действий, выполняемых независимо от результата try и catch, например для освобождения ресурсов. Поэтому его исключение рассматривается как самостоятельный возможный результат метода.
В примере IllegalStateException действительно перехватывается в catch, но после этого управление переходит в finally. Там создаётся проверяемое IOException, которое не перехватывается внешним обработчиком.
Если убрать throws IOException, компилятор сообщит о необработанном проверяемом исключении. На практике это означает, что даже полностью обработанный основной сценарий не освобождает метод от требования описать исключение из завершающей логики.
Компилятор рассматривает finally как обязательную часть каждого пути выхода из try-конструкции. Это относится к обычному завершению, исключению из try, обработке в catch, return, break и continue.
Минимальный вариант исправления:
Во время выполнения исключение из finally становится фактическим исключением, покидающим конструкцию. Если до finally существовало другое исключение, оно обычно теряется как основной результат; для finally нет автоматического механизма подавления, аналогичного try-with-resources.
Поэтому явный throw из finally считается опасным: он может скрыть исходную ошибку. Обычно в finally выполняют очистку, не выбрасывающую исключение наружу, либо явно сохраняют исходную ошибку и добавляют ошибку очистки как подавленную через addSuppressed.
Сервис записывает данные и в finally закрывает устаревший клиент:
Если save завершился ошибкой, а close выбросил другую, вызывающий код увидит ошибку закрытия и может потерять причину сбоя записи. Вариант с подавлением сохраняет обе причины, но требует аккуратного кода и гарантии, что исходная ошибка не будет затёрта.
Предпочтительное решение — использовать try-with-resources, когда объект является ресурсом. Java автоматически делает исключение из основного тела первичным, а исключение закрытия — подавленным. Это улучшает диагностику и уменьшает вероятность ошибки ручной очистки.
Перехватит ли внешний catch исключение из finally?
Да, если catch расположен снаружи всей конструкции try-catch-finally. Внутренний catch уже завершил свою работу и не может обработать исключение, выброшенное последующим finally.
Что произойдёт, если finally завершится нормально после обработанного исключения?
Исключение из try останется обработанным, и метод продолжит выполнение после всей конструкции. Сам факт наличия finally не повторно выбрасывает исключение: это происходит только при явном throw, другом исключении или управляющем операторе, меняющем результат выхода.
Почему try-with-resources безопаснее ручного finally для ошибок закрытия?
При исключении в теле конструкции оно становится основным. Исключение из close() добавляется к нему через механизм подавленных исключений и доступно через getSuppressed().
Если тело завершилось нормально, исключение из close() становится основным и передаётся вызывающему коду. Таким образом, try-with-resources не скрывает ошибку закрытия, но корректно различает её приоритет относительно ошибки основного тела.