W3docs

Java try-with-resources

Автоматически закрывайте AutoCloseable-ресурсы в Java с помощью оператора try-with-resources.

Оператор try-with-resources — это решение Java для паттерна «открыл, использовал, всегда закрой». Вы объявляете ресурс в начале блока try, и JVM гарантирует его закрытие при выходе из блока — независимо от того, произошёл ли нормальный возврат, было ли выброшено исключение или блок был прерван каким-либо другим управляющим потоком. Многословный танец с finally сворачивается в одно объявление.

В этой главе рассматриваются синтаксис, что считается ресурсом, порядок закрытия нескольких ресурсов, механизм подавленных исключений, делающий эту возможность надёжной, и случаи, когда данный оператор применять не следует.

Синтаксис

try (FileReader reader = new FileReader(path)) {
  // use reader
}

Вот и всё. Никакого finally, никакого явного close(). При выходе из try JVM автоматически вызывает reader.close(). Блоки catch и finally, если вы их добавите, по-прежнему работают — они выполняются после закрытия ресурса.

try (FileReader reader = new FileReader(path)) {
  // use reader
} catch (IOException e) {
  // handle — at this point the reader is already closed
}

Что может быть ресурсом

Ресурс должен реализовывать AutoCloseable (или его более старый подинтерфейс Closeable). Этот интерфейс объявляет единственный метод:

public interface AutoCloseable {
  void close() throws Exception;
}

Большинство типов, к которым вы обратитесь, уже реализуют его: InputStream, OutputStream, Reader, Writer, Connection, Statement, ResultSet, Scanner, обёртки Lock и многие сторонние клиенты. Если вы пишете класс, владеющий ресурсом, реализуйте AutoCloseable самостоятельно.

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

Объявления разделяются точкой с запятой. Ресурсы закрываются в обратном порядке объявления — последний открытый закрывается первым:

try (
  FileInputStream  in  = new FileInputStream(src);
  FileOutputStream out = new FileOutputStream(dst)
) {
  in.transferTo(out);
}
// closes `out` first, then `in`

Обратный порядок важен, потому что второй ресурс нередко зависит от первого. Закрытие зависимого ресурса первым оставляет фундамент нетронутым ещё на мгновение — это более безопасный порядок.

Повторное использование существующих ресурсов (Java 9+)

Если у вас уже есть переменная, хранящая AutoCloseable, объявлять новую не нужно — передайте её по имени:

BufferedReader reader = openSomewhere();
try (reader) {
  // use it
}

Переменная должна быть final или effectively final (её нельзя переприсваивать). Эта форма удобна, когда ресурс создаётся в другом месте — ценой меньшей видимости закрытия. Используйте её, когда существующая переменная находится прямо здесь; в противном случае предпочтите встроенное объявление.

Подавленные исключения

Вот тонкий момент. Что произойдёт, если тело try выбросит исключение и вызов close() тоже выбросит исключение? До Java 7 исключение из finally при закрытии заменяло исходное — и вы теряли настоящую причину.

try-with-resources решает эту проблему. Исходное исключение передаётся вызывающему коду; любые исключения, возникшие при закрытии, прикрепляются к нему как подавленные, и их можно получить через Throwable.getSuppressed():

try (Resource r = new Resource()) {
  r.use();          // throws A
}                   // close() throws B
// caller sees A
// A.getSuppressed() returns [B]

Стандартный printStackTrace() также выводит подавленные исключения (строки Suppressed: ... под основным). Вам почти никогда не придётся обрабатывать это вручную — язык сохраняет цепочку за вас.

Типичный реальный пример

Распространённый паттерн, объединяющий всё вместе:

public List<String> readAllLines(Path p) throws IOException {
  try (BufferedReader reader = Files.newBufferedReader(p)) {
    return reader.lines().toList();
  }
}

Обратите внимание: нет явного закрытия, нет finally, тело может напрямую использовать return, а вызывающий код всё равно получит чистое IOException, если что-то пошло не так.

Когда try-with-resources — не тот инструмент

Это правильный инструмент, когда вы владеете полным временем жизни ресурса в пределах одного блока. Он не подходит, когда:

  • Ресурс живёт дольше блока — например, соединение, кешируемое между вызовами. Его закрытие в конце метода сломает следующего пользователя.
  • Вы не владеете ресурсом — его передал вам вызывающий код. Он закроет его сам.

В таких случаях оставьте закрытие владельцу. Если вы не можете определить, кто является владельцем, это признак проблемы проектирования — разберитесь с этим до написания кода.

Проработанный пример

Короткая программа, которая копирует строку во внутрипроцессный канал и считывает её обратно через буферизованный ридер. Оба потока реализуют AutoCloseable; оба закрываются автоматически; мы добавляем небольшой пользовательский ресурс, чтобы вы могли увидеть порядок закрытия в выводе.

java— editable, runs on the server

В выводе вы можете видеть, что порядок объявления — a, b, src, buf, но порядок закрытия обратный: buf, src, b, a. Это гарантия языка — первый открытый, последний закрытый.

Что дальше

До сих пор мы только перехватывали исключения, которые Java бросала на нас. Реальный код также должен генерировать собственные — для недопустимых аргументов, нарушенных инвариантов или доменных ошибок. Продолжайте изучение в разделе Java throw и throws.

Практика

Практика
Блок `try-with-resources` открывает два ресурса `AutoCloseable`. Тело выбрасывает `IOException`, а `close()` второго ресурса тоже выбрасывает `IOException`. Что видит вызывающий код?
Блок `try-with-resources` открывает два ресурса `AutoCloseable`. Тело выбрасывает `IOException`, а `close()` второго ресурса тоже выбрасывает `IOException`. Что видит вызывающий код?
Was this page helpful?