Питання 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]]
  • [[Які винятки потрібно обробляти обов’язково]]