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

Что такое try-with-resources?

Раньше нужно было вручную закрывать ресурсы в finally:

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

Junior Level

Определение

Try-with-resources (TWR) — это конструкция Java 7+. Минимальная версия: Java 7. Для Java 6 и ниже — используйте finally. В Android — API 19+ (KitKat). Автоматически закрывает ресурсы после выхода из блока try.

Зачем нужен

Раньше нужно было вручную закрывать ресурсы в finally:

// Старый подход (до Java 7)
BufferedReader br = null;
try {
    br = new BufferedReader(new FileReader("file.txt"));
    br.readLine();
} finally {
    if (br != null) br.close(); // Руками!
}

Теперь автоматически:

// Современный подход (Java 7+)
try (BufferedReader br = new BufferedReader(new FileReader("file.txt"))) {
    br.readLine();
} // br.close() вызовется автоматически!

Какие ресурсы можно использовать

Любые, реализующие AutoCloseable:

  • FileInputStream, BufferedReader — файлы
  • Connection, Statement — база данных
  • Socket — сеть

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

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

Когда НЕ использовать try-with-resources

  1. Фабричные методы – создаёте ресурс и возвращаете вызывающему (TWR закроет его до return)
  2. Ресурс живёт дольше одного метода – lifecycle управляется извне
  3. Пулированные ресурсы (HikariCP connection) – возврат в пул != закрытие, пул сам управляет

Middle Level

Как это работает

Компилятор разворачивает TWR в код с finally и обработкой suppressed исключений:

// Ваш код
try (Resource r = new Resource()) {
    r.doWork();
}

// Компилятор создаёт этот шаблон, чтобы:
// (1) гарантировать закрытие ресурса
// (2) не потерять исключение из close() если в try уже была ошибка
//     -- оно добавляется в suppressed-список primaryException
// Разворачивается в:
Resource r = new Resource();
Throwable primaryException = null;
try {
    r.doWork();
} catch (Throwable t) {
    primaryException = t;
    throw t;
} finally {
    if (r != null) {
        if (primaryException != null) {
            try { r.close(); }
            catch (Throwable t) { primaryException.addSuppressed(t); }
        } else {
            r.close();
        }
    }
}

Suppressed Exceptions

Если в try упало исключение #1, а при close() — исключение #2:

  • #1 остаётся основным
  • #2 добавляется как suppressed (подавленное)
  • Вы получаете полную картину: Error A (Suppressed: Error B)

Получить suppressed: exception.getSuppressed()

Эффект ловушки вложенных ресурсов

// ОПАСНО — FileWriter может утечь!
try (BufferedWriter writer = new BufferedWriter(new FileWriter("file.txt"))) {
    // Если конструктор BufferedWriter упадёт, FileWriter НЕ закроется
}

// БЕЗОПАСНО — каждый ресурс отдельно
try (FileWriter fw = new FileWriter("file.txt");
     BufferedWriter writer = new BufferedWriter(fw)) {
    writer.write("data");
}

Java 9+ — effectively final

Можно использовать внешние переменные:

BufferedReader br = createReader();
try (br) { // br — effectively final
    br.readLine();
}

Senior Level

Порядок закрытия (LIFO)

Ресурсы закрываются в порядке, обратном их объявлению:

LIFO (Last In, First Out) – последний открытым, первый закрытым. Как стопка тарелок: последнюю положил – первую берёшь. Для ресурсов это значит: самый «внутренний» ресурс закрывается первым.

try (Socket socket = new Socket("localhost", 8080);
     OutputStream os = socket.getOutputStream();
     BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(os))) {
    // Закроются: writer → os → socket
}

Это критично для стеков потоков. Если закрыть Socket раньше Stream, Stream упадёт при попытке flush.

Null Resources

Если ресурс в скобках инициализирован как null, TWR просто проигнорирует его — NPE не будет.

Производительность: Suppressed Overhead

При закрытии 1000 ресурсов и массовом сбое, объект основного исключения содержит огромный список suppressed. Это может ударить по памяти.

Edge Cases

  • Catch/Finally порядок — ваши catch и finally выполняются после закрытия ресурсов
  • Внутри catch ресурсы уже недоступны (закрыты)
  • Компилятор создаёт скрытую локальную переменную для внешних ресурсов (Java 9+)

Диагностика

  • Throwable.getSuppressed() — всегда проверяйте этот массив при проблемах с инфраструктурой
  • Static Analysis — SonarLint находит ресурсы, созданные внутри try блока, но не в скобках
  • javap -v — посмотрите сгенерированный байт-код для понимания механизма

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

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

  • Try-with-resources (TWR) — Java 7+, автоматически закрывает ресурсы после выхода из try
  • Ресурс обязан реализовывать AutoCloseable
  • Компилятор разворачивает TWR в finally с обработкой suppressed исключений
  • Suppressed exceptions: если в try и close() упали два — #1 основное, #2 suppressed
  • Порядок закрытия — LIFO (последний открытым, первый закрытым)
  • Эффект ловушки вложенных ресурсов: new B(new A()) — если B упадёт, A утечёт
  • Java 9+ поддерживает effectively final внешние переменные
  • Null ресурс в TWR просто игнорируется — NPE не будет

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

  • Почему вложенные ресурсы опасны? — Если конструктор внешнего ресурса упадёт, внутренний утечёт; объявляйте каждый отдельно
  • В каком порядке закрываются ресурсы? — LIFO: последний объявленным закрывается первым
  • Что будет если ресурс = null? — TWR проигнорирует его, NPE не возникнет
  • Когда НЕ использовать TWR? — Фабричные методы (возврат из метода), пулированные ресурсы, ресурс живёт дольше метода

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

  • “Я создаю ресурсы внутри try, но не в скобках” — они не закроются автоматически
  • “При массовом сбое suppressed исключения не важны” — они могут содержать критичную диагностику
  • “TWR работает только с InputStream” — работает с любым AutoCloseable

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

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