Питання 12 · Розділ 7

В чому різниця між AutoCloseable та Closeable?

Closeable — це приватний випадок AutoCloseable лише для I/O операцій.

Мовні версії: English Russian Ukrainian

Junior Level

Коротка відповідь

Closeable — це приватний випадок AutoCloseable лише для I/O операцій.

AutoCloseable (java.lang)
  └── Closeable (java.io)
        └── FileInputStream, BufferedReader, тощо.

Основні відмінності

Характеристика Closeable AutoCloseable
Пакет java.io java.lang
Виняток throws IOException throws Exception
Призначення Лише I/O Будь-які ресурси

Приклади

// Closeable — лише I/O
try (FileInputStream fis = new FileInputStream("file.txt")) {
    fis.read();
} // close() кидає IOException

// AutoCloseable — будь-який ресурс
try (Connection conn = dataSource.getConnection()) {
    conn.createStatement().executeQuery("SELECT 1");
} // close() кидає SQLException

Який використовувати

  • Для файлів, потоків — Closeable (вже реалізований у JDK)
  • Для своїх ресурсів (БД, транзакції) — AutoCloseable

Middle Level

Ієрархія (з Java 7)

Closeable успадковує AutoCloseable. Це зроблено для зворотної сумісності — усі старі I/O класи працюють у try-with-resources.

Різниця в контрактах

Ідемпотентність:

  • Closeable – Javadoc вимагає ідемпотентності: “якщо потік вже закритий, виклик не має ефекту”
  • AutoCloseable – Javadoc рекомендує: “настійно рекомендується…”
  • На практиці обидва повинні бути ідемпотентними.

Причина: деякі ресурси (транзакції) можуть викидати виняток при спробі закрити вже закриту транзакцію.

Flushable & Closeable

Багато Closeable класів також реалізують Flushable:

Flushable – інтерфейс з методом flush(). Flush = скинути буферизовані дані. BufferedOutputStream.close() спочатку викликає flush(), щоб дані з буфера записалися на диск.

public class BufferedOutputStream extends FilterOutputStream
    implements Flushable, Closeable {

    public void close() throws IOException {
        flush(); // Скидає буфер перед закриттям
        out.close();
    }
}

При закритті close() зазвичай викликає flush(). Але це залежить від реалізації.

Exception Narrowing

При реалізації AutoCloseable для своїх сервісів, прибирайте throws Exception:

// Погано
public class MyService implements AutoCloseable {
    public void close() throws Exception { }
}

// Добре
public class MyService implements AutoCloseable {
    public void close() { } // Без throws — чисто і безпечно
}

Що реалізовувати у своєму класі

  • Closeable – якщо ваш клас – I/O потік (працює з байтами/символами)
  • AutoCloseable – якщо ваш клас – будь-який інший ресурс (БД-з’єднання, транзакція, блокування)

Senior Level

Suppressed Exceptions Priority

При закритті кількох ресурсів у TWR, порядок – LIFO (Last In, First Out). Виняток від найвнутрішнішого ресурсу додається останнім у suppressed список.

// При закритті A, B, C (у цьому порядку), якщо всі три впали: // A – основний виняток // B і C – suppressed (порядок: B потім C, LIFO)

Interrupted Close

Якщо close() блокує (закриття мережевого пулу) і потік переривається — InterruptedException. TWR не вміє магічно обробляти переривання — все одно потрібен catch.

Static Analysis

instanceof Closeable також означає instanceof AutoCloseable. Але не навпаки!

Edge Cases

  • Транзакційні ресурсиclose() може означати “commit”. Повторний виклик повинен бути безпечним
  • Shared ресурси — якщо ресурс використовується кількома компонентами, close() одного не повинен ламати інших

Діагностика

У Highload-системах витік дескрипторів файлів через неправильний вибір між цими інтерфейсами у кастомних обгортках — одна з найчастіших причин падіння JVM через кілька днів роботи.

  • lsof -p <pid> — покаже відкриті файлові дескриптори
  • jstack — покаже заблоковані потоки у close()
  • Метрики — відстежуйте кількість відкритих з’єднань через Micrometer

🎯 Шпаргалка для співбесіди

Обов’язково знати:

  • Closeable успадковується від AutoCloseable (ієрархія: AutoCloseableCloseable)
  • Closeable у java.io, кидає IOException; AutoCloseable у java.lang, кидає Exception
  • Closeable — лише для I/O потоків; AutoCloseable — для будь-яких ресурсів (БД, транзакції, блокування)
  • Обидва повинні бути ідемпотентними, але для Closeable це суворіша вимога Javadoc
  • При реалізації звужуйте тип винятку або прибирайте throws повністю
  • Порядок закриття у TWR — LIFO (останнім відкритим закривається першим)

Часті уточнюючі запитання:

  • Який інтерфейс реалізовувати у своєму класі?Closeable якщо це I/O потік, AutoCloseable для всього іншого
  • Що таке Exception Narrowing? — При реалізації AutoCloseable замінюйте throws Exception на конкретний тип або прибирайте
  • Чому Closeable кидає IOException? — Історично: створений для I/O операцій, де IOException — єдина очікувана помилка
  • Що буде якщо закрити спільний ресурс з двох компонентів? — Другий виклик повинен бути безпечним (ідемпотентність); інакше — баг архітектури

Червоні прапорці (НЕ говорити):

  • “Це одне й те саме” — У них різні пакети, контракти та призначення
  • “AutoCloseable кидає лише RuntimeException” — Кидає Exception (checked), але ви можете звузити
  • “Порядок закриття не важливий” — LIFO критичний: найзовнішніший ресурс закривається останнім

Пов’язані теми:

  • [[Що таке інтерфейс AutoCloseable]]
  • [[Що таке try-with-resources]]
  • [[Які вимоги до ресурсів в try-with-resources]]
  • [[Що станеться, якщо в блоку finally теж виникне виняток]]
  • [[Що таке обгортання винятків (wrapping)]]