Программирование JavaJava CoreJava-разработчик серверных приложений

Практическая ситуация: определите, какое исключение увидит вызывающий код после выполнения метода run. прим...

Практическая ситуация: определите, какое исключение увидит вызывающий код после выполнения метода 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");
        }
    }
}
Проходите собеседования с ИИ помощником Hintsage

Краткий ответ

Вызывающий код получит IllegalArgumentException("body"), возникшее в теле try. Исключение IllegalStateException("close"), возникшее при автоматическом закрытии ресурса, будет добавлено к нему как подавленное исключение и доступно через getSuppressed().

Исторический контекст

Конструкция try-with-resources появилась в Java 7, чтобы безопасно закрывать объекты, реализующие AutoCloseable, без ручного дублирования вызовов close() в блоке finally. Она решает проблему утечек ресурсов и неоднозначности при одновременной ошибке в основном коде и при освобождении ресурса.

Постановка проблемы

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

Неверная ручная реализация через finally может потерять первое исключение. Это усложняет диагностику и может скрыть реальную причину отказа операции.

Подробное решение

При выходе из try Java автоматически вызывает close() ресурса. Если тело try уже завершилось исключением, это исключение становится основным, а исключение из close() добавляется к нему методом addSuppressed.

Упрощённо поведение можно представить так:

try { Resource resource = new Resource(); try { throw new IllegalArgumentException("body"); } catch (Throwable primary) { try { resource.close(); } catch (Throwable closing) { primary.addSuppressed(closing); } throw primary; } }

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

try { Demo.run(); } catch (Exception exception) { System.out.println(exception.getMessage()); for (Throwable suppressed : exception.getSuppressed()) { System.out.println(suppressed.getMessage()); } }

Сначала будет напечатано body, затем close. Если тело try завершилось нормально, а ошибка возникла только в close(), закрывающая ошибка станет основной и будет выброшена вызывающему коду.

Ресурсы в конструкции try (first; second) закрываются в обратном порядке: сначала second, затем first. Если основной ошибки нет и закрытие нескольких ресурсов завершается ошибками, первая возникшая закрывающая ошибка становится основной, а остальные добавляются к ней как подавленные.

Ситуация из практики

Сервис читает данные из файла и должен гарантированно закрыть файловый поток. Вариант с ручным finally может случайно выбросить исключение из close() вместо ошибки чтения. Его плюс — совместимость со старым кодом, но минусы — больший объём шаблонного кода и риск потери первичного исключения.

Вариант с try-with-resources автоматически управляет временем жизни ресурса и сохраняет обе ошибки с правильным приоритетом. Поэтому для AutoCloseable-ресурсов выбирают именно его: первопричина остаётся основной, а сбой закрытия не теряется. При расследовании инцидента следует логировать и основное, и подавленные исключения.

Что кандидаты часто упускают

  1. Что произойдёт, если исключение возникнет только в close()?

    Тогда исключение из close() станет основным и будет передано вызывающему коду. Подавленных исключений не будет, если другие операции закрытия не завершились ошибками.

  2. В каком порядке закрываются несколько ресурсов?

    Ресурсы закрываются в порядке, обратном их объявлению. Для try (A a = ...; B b = ...) сначала закрывается b, затем a. Это соответствует принципу стека: последний приобретённый ресурс освобождается первым.

  3. Что будет, если close() выбрасывает Error, а тело try — исключение?

    Механизм подавления применяется к объектам типа Throwable, поэтому Error из close() также может быть добавлен к основному исключению через getSuppressed(). Однако подавление не делает Error безопасным для игнорирования: это обычно серьёзный сбой, который должен корректно обрабатываться на границе приложения.