Вопрос 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, etc.

Основные отличия

Характеристика 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 критичен: самый внешний ресурс закрывается последним

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

  • [[11. Что такое AutoCloseable интерфейс]]
  • [[9. Что такое try-with-resources]]
  • [[10. Какие требования к ресурсам в try-with-resources]]
  • [[23. Что такое suppressed exceptions]]
  • [[18. Что такое оборачивание (wrapping) исключений]]