Практическая ситуация: определите, какое исключение увидит вызывающий код после выполнения метода run.
class Demo {
static class Resource implements AutoCloseable {
@Override
public void close() {
throw new IllegalStateException("close");
}
}
static void run() {
try (Resource resource = new Resource()) {
throw new IllegalArgumentException("body");
}
}
}
Вызывающий код получит IllegalArgumentException("body"), возникшее в теле try. Исключение IllegalStateException("close"), возникшее при автоматическом закрытии ресурса, будет добавлено к нему как подавленное исключение и доступно через getSuppressed().
Конструкция try-with-resources появилась в Java 7, чтобы безопасно закрывать объекты, реализующие AutoCloseable, без ручного дублирования вызовов close() в блоке finally. Она решает проблему утечек ресурсов и неоднозначности при одновременной ошибке в основном коде и при освобождении ресурса.
При работе с файлами, потоками, соединениями и другими ресурсами ошибка может возникнуть как в теле операции, так и во время закрытия ресурса. Если закрывающая ошибка заменит исходную, разработчик увидит не первопричину сбоя, а вторичную проблему освобождения ресурса.
Неверная ручная реализация через finally может потерять первое исключение. Это усложняет диагностику и может скрыть реальную причину отказа операции.
При выходе из try Java автоматически вызывает close() ресурса. Если тело try уже завершилось исключением, это исключение становится основным, а исключение из close() добавляется к нему методом addSuppressed.
Упрощённо поведение можно представить так:
Фактическая реализация компилятором сложнее и учитывает корректное закрытие ресурса, но принцип выбора основного исключения именно такой. Проверить вторичную ошибку можно следующим образом:
Сначала будет напечатано body, затем close. Если тело try завершилось нормально, а ошибка возникла только в close(), закрывающая ошибка станет основной и будет выброшена вызывающему коду.
Ресурсы в конструкции try (first; second) закрываются в обратном порядке: сначала second, затем first. Если основной ошибки нет и закрытие нескольких ресурсов завершается ошибками, первая возникшая закрывающая ошибка становится основной, а остальные добавляются к ней как подавленные.
Сервис читает данные из файла и должен гарантированно закрыть файловый поток. Вариант с ручным finally может случайно выбросить исключение из close() вместо ошибки чтения. Его плюс — совместимость со старым кодом, но минусы — больший объём шаблонного кода и риск потери первичного исключения.
Вариант с try-with-resources автоматически управляет временем жизни ресурса и сохраняет обе ошибки с правильным приоритетом. Поэтому для AutoCloseable-ресурсов выбирают именно его: первопричина остаётся основной, а сбой закрытия не теряется. При расследовании инцидента следует логировать и основное, и подавленные исключения.
Что произойдёт, если исключение возникнет только в close()?
Тогда исключение из close() станет основным и будет передано вызывающему коду. Подавленных исключений не будет, если другие операции закрытия не завершились ошибками.
В каком порядке закрываются несколько ресурсов?
Ресурсы закрываются в порядке, обратном их объявлению. Для try (A a = ...; B b = ...) сначала закрывается b, затем a. Это соответствует принципу стека: последний приобретённый ресурс освобождается первым.
Что будет, если close() выбрасывает Error, а тело try — исключение?
Механизм подавления применяется к объектам типа Throwable, поэтому Error из close() также может быть добавлен к основному исключению через getSuppressed(). Однако подавление не делает Error безопасным для игнорирования: это обычно серьёзный сбой, который должен корректно обрабатываться на границе приложения.