Вопрос 10 · Раздел 7

Какие требования к ресурсам в try-with-resources?

Ресурс обязан реализовывать интерфейс java.lang.AutoCloseable. Без этого его нельзя использовать в try-with-resources.

Версии по языкам: English Russian Ukrainian

Junior Level

Главное требование

Ресурс обязан реализовывать интерфейс java.lang.AutoCloseable. Без этого его нельзя использовать в try-with-resources.

// Работает — FileInputStream реализует AutoCloseable
try (FileInputStream fis = new FileInputStream("file.txt")) {
    fis.read();
}

// Не скомпилируется — String не реализует AutoCloseable
try (String s = "hello") { } // Ошибка компиляции

Какие классы реализуют AutoCloseable

  • Все InputStream / OutputStream — файлы, сеть
  • Все Reader / Writer — текст
  • Connection, Statement, ResultSet — база данных
  • Scanner, Formatter — утилиты

Несколько ресурсов

try (FileInputStream fis = new FileInputStream("in.txt");
     FileOutputStream fos = new FileOutputStream("out.txt")) {
    // Оба закроются автоматически
}

Java 9+ — внешние переменные

FileInputStream fis = createStream();
try (fis) { // fis должен быть effectively final
    fis.read();
}

Когда НЕ класть ресурс в try-with-resources

  1. Ресурс управляется контейнером (Spring @Bean) – фреймворк сам закроет
  2. Соединение из пула (HikariCP) – пул управляет lifecycle за вас
  3. Нужно передать ресурс дальше – return из метода, TWR закроет до возврата

Middle Level

AutoCloseable vs Closeable

AutoCloseable (Java 7, java.lang):

  • void close() throws Exception
  • Универсальный — для любых ресурсов

Closeable (Java 5, java.io):

  • void close() throws IOException
  • Только для I/O
  • Наследует AutoCloseable

Для try-with-resources подходит любой из них.

Риск утечки при инициализации

// БЕЗОПАСНО — если B упадёт, A закроется
try (A a = new A(); B b = new B(a)) { }

// ОПАСНО — если B упадёт, A утечёт
try (B b = new B(new A())) { }
// A создан, но try не имеет на него ссылки
// Разбор утечки: new A() создал объект. new B(a) бросил исключение.
// A создан, но переменная в TWR -- это B. У TWR нет ссылки на A,
// поэтому close() для A не вызовется. Решение:
// try (A a = new A(); B b = new B(a)) { ... }

Идемпотентность close()

Метод close() должен быть идемпотентным — повторный вызов не должен выбрасывать исключение.

Идемпотентность – свойство операции: повторный вызов даёт тот же результат, что и первый. close() должен быть идемпотентным: первый вызов закрывает ресурс, второй – ничего не делает (не бросает исключение).

public void close() {
    if (closed.compareAndSet(false, true)) {
        // реальное освобождение ресурсов
    }
}

Требования к переменным (Java 9+)

Ресурс должен быть final или effectively final. Компилятор создаёт локальную копию ссылки — это гарантирует, что если другой поток изменит внешнюю переменную, TWR всё равно закроет оригинальный объект.

effectively final = переменная не меняется после инициализации, даже если ключевое слово final не написано. Компилятор видит, что вы не меняете переменную, и разрешает использовать в TWR.


Senior Level

Under the Hood: компилятор создаёт копию

При использовании внешней переменной в try(resource), компилятор создаёт локальную копию ссылки. Это гарантирует потокобезопасность закрытия.

Бросание исключений из close()

Если close() бросает исключение:

  • Если в try ошибок не было — исключение из close() станет основным
  • Если в try уже было исключение — из close() добавится в suppressed

Interrupted close

В сетевых операциях close() может заблокироваться. Устанавливайте тайм-ауты, иначе поток “зависнет” на стадии закрытия ресурсов.

Object Allocation и GC

try-with-resources — создание объектов. При открытии/закрытии тысяч ресурсов в секунду это создаёт давление на GC (Young Generation). Используйте пулы ресурсов (HikariCP для БД), которые реализуют AutoCloseable, но вместо закрытия возвращают объект в пул.

Edge Cases

  • Ресурс не открылся — если конструктор бросил исключение, TWR не будет закрывать (нечего закрывать)
  • Частичная инициализация — если конструктор открыл нативный ресурс, но упал до возврата — утечка. Делайте конструкторы лёгкими.

Диагностика

  • Resource Leak Detection — IDE подсвечивает AutoCloseable классы, не использованные в TWR. Не игнорируйте.
  • jstack analysis — много потоков в BLOCKED / WAITING в close() — проверьте дедлоки между ресурсами.

🎯 Шпаргалка для интервью

Обязательно знать:

  • Ресурс обязан реализовывать java.lang.AutoCloseable
  • AutoCloseable (Java 7, java.lang) — close() throws Exception; Closeable (Java 5, java.io) — close() throws IOException
  • Метод close() должен быть идемпотентным — повторный вызов не бросает исключение
  • Ресурсы объявляйте отдельно: try (A a = new A(); B b = new B(a)) — иначе утечка
  • Java 9+: внешние переменные должны быть effectively final
  • Компилятор создаёт локальную копию ссылки — гарантирует потокобезопасность закрытия
  • Если close() бросает при наличии ошибки в try — добавляется в suppressed
  • Пулы ресурсов (HikariCP) реализуют AutoCloseable, но возвращают объект в пул, а не закрывают

Частые уточняющие вопросы:

  • В чём разница AutoCloseable и Closeable? — AutoCloseable универсальнее (throws Exception), Closeable только для I/O (throws IOException)
  • Что такое идемпотентность close()? — Первый вызов закрывает ресурс, второй — ничего не делает
  • Почему вложенное создание ресурсов опасно?new B(new A()) — если B упадёт, у TWR нет ссылки на A, он утечёт
  • Что происходит при массовом открытии/закрытии ресурсов? — Давление на GC; используйте пулы ресурсов

Красные флаги (НЕ говорить):

  • “Close() может бросать исключение каждый раз” — должен быть идемпотентным
  • “Я не объявляю ресурсы отдельно, чтобы короче написать” — это вызывает утечки
  • “TWR закрывает соединение из пула” — пул управляет lifecycle сам, возврат != закрытие

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

  • [[Что такое try-with-resources]]
  • [[Что такое AutoCloseable интерфейс]]
  • [[В чём разница между AutoCloseable и Closeable]]
  • [[Что такое suppressed exceptions]]
  • [[Какие исключения нужно обрабатывать обязательно]]