I/O ·
‹ Предыдущий Следующий ›
⏱ 5 минут чтения Обновлено: 2026-10-31

Конструкция try-with-resources в Java

В классическом try-finally есть неочевидный дефект: если br.readLine() выбросил IOException, а затем br.close() в блоке finally тоже упал, наружу уйдёт только исключение из close() — настоящая причина сбоя бесследно теряется. Конструкция try-with-resources решает и эту проблему, и проблему забытого вызова close().

try-with-resources — это форма оператора try (появилась в Java 7), в которой ресурсы объявляются в круглых скобках после try и закрываются автоматически: метод close() вызывается при выходе из блока try — и при обычном завершении, и при исключении. Ресурсом может быть любой объект, класс которого реализует интерфейс java.lang.AutoCloseable (в том числе его наследник java.io.Closeable): потоки ввода-вывода, сокеты, JDBC-соединения, Scanner.

Синтаксис try-with-resources

Ресурс объявляется и инициализируется в круглых скобках сразу после ключевого слова try; несколько ресурсов разделяются точкой с запятой. Блоки catch и finally необязательны: если исключения объявлены в throws метода, достаточно одного блока try.

try (ResourceType1 name1 = new ResourceType1(...);
     ResourceType2 name2 = new ResourceType2(...)) {
    // работа с ресурсами
} catch (IOException e) {
    // необязательно
} finally {
    // необязательно
}

Переменная ресурса неявно final и видна только внутри блока try: в catch и finally она уже недоступна, а ресурс к этому моменту закрыт.

try-finally и try-with-resources: сравнение

До Java 7 ресурс закрывали вручную в блоке finally; try-with-resources делает то же самое автоматически, короче и надёжнее: не нужно писать вызов close(), а исключение из close() не затирает исходное. Сравним два варианта метода, читающего первую строку файла.

Пример 1. Закрытие ресурса в блоке finally

public static String readFirstLineWithFinally(String path) throws IOException {
    BufferedReader br = new BufferedReader(new FileReader(path));
    try {
        return br.readLine();
    } finally {
        br.close();
    }
}

Проверка br != null здесь не нужна: если файла нет, new FileReader(path) выбросит FileNotFoundException ещё до входа в блок try, и переменная просто не будет создана.

Перепишем этот метод с помощью try-with-resources.

Пример 2. Тот же метод с try-with-resources

public static String readFirstLine(String path) throws IOException {
    try (BufferedReader br = new BufferedReader(new FileReader(path))) {
        return br.readLine();
    }
}

// Файл содержит строки "Hello" и "World":
// readFirstLine("a.txt")       -> "Hello"
// readFirstLine("missing.txt") -> java.io.FileNotFoundException
Критерий try-finally try-with-resources
Закрытие ресурса Вручную, вызовом close() в finally Автоматически при выходе из блока try
Исключение из close() Заменяет исходное исключение из try — причина сбоя теряется Добавляется к исходному как подавленное (getSuppressed())
Несколько ресурсов Вложенные try-finally для каждого ресурса Одни скобки, ресурсы через ;, закрытие в обратном порядке
Требования к ресурсу Любой объект с методом закрытия Класс должен реализовывать AutoCloseable
Версия Java Любая Java 7+; ресурс-переменная из внешнего кода — Java 9+

Несколько ресурсов и порядок закрытия

В одном try-with-resources можно объявить несколько ресурсов через точку с запятой; они открываются слева направо, а закрываются в обратном порядке — последний открытый закрывается первым. Если конструктор второго ресурса выбросит исключение, уже открытый первый ресурс всё равно будет закрыт.

Пример 3. Два ресурса в одном блоке try

public static String readFirstLine2(String path) throws IOException {
    try (FileReader fr = new FileReader(path);
         BufferedReader br = new BufferedReader(fr)) {
        return br.readLine();
    }
}

Сначала закроется br, затем fr. BufferedReader.close() сам закрывает вложенный FileReader, поэтому второй вызов close() для fr ничего не делает — это безопасно, потому что повторное закрытие Closeable не должно давать эффекта.

Порядок хорошо виден на собственном ресурсе, который печатает сообщения при открытии и закрытии.

Пример 4. Порядок открытия и закрытия ресурсов

public class CloseOrderDemo {

    static class Resource implements AutoCloseable {
        private final String name;

        Resource(String name) {
            this.name = name;
            System.out.println("Открыт " + name);
        }

        @Override
        public void close() {
            System.out.println("Закрыт " + name);
        }
    }

    public static void main(String[] args) {
        try (Resource a = new Resource("A");
             Resource b = new Resource("B")) {
            System.out.println("Работа с ресурсами");
        }
    }
}

// Вывод:
// Открыт A
// Открыт B
// Работа с ресурсами
// Закрыт B
// Закрыт A

Обратите внимание: метод close() здесь объявлен без throws Exception, поэтому блок catch не нужен. Если бы close() бросал проверяемое исключение, компилятор потребовал бы его обработать или объявить в throws.

AutoCloseable и Closeable: что может быть ресурсом

Ресурсом в try-with-resources может быть только объект, класс которого реализует java.lang.AutoCloseable. Интерфейс java.io.Closeable расширяет AutoCloseable, поэтому все потоки ввода-вывода из java.io подходят автоматически. Разница между интерфейсами — в сигнатуре close() и требованиях к ней.

Интерфейс Пакет и версия Сигнатура close() Повторный вызов close()
AutoCloseable java.lang, Java 7 void close() throws Exception Может иметь побочные эффекты
Closeable java.io, Java 5; с Java 7 наследует AutoCloseable void close() throws IOException Должен ничего не делать (идемпотентность)

При реализации собственного ресурса сужайте исключение в close(): объявите throws IOException или вообще не объявляйте проверяемых исключений, как в примере 4. Тогда вызывающему коду не придётся ловить общий Exception.

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

Если исключение выброшено в блоке try, а затем close() тоже выбрасывает исключение, наружу уходит исключение из блока try, а исключение из close() прикрепляется к нему методом addSuppressed(). Получить подавленные исключения можно методом Throwable.getSuppressed(), который возвращает массив Throwable[].

Пример 5. Что выведет этот код?

public class SuppressedDemo {

    static class FaultyResource implements AutoCloseable {
        @Override
        public void close() {
            throw new IllegalStateException("ошибка в close()");
        }
    }

    public static void main(String[] args) {
        // Вариант 1: try-finally
        try {
            FaultyResource r = new FaultyResource();
            try {
                throw new RuntimeException("ошибка в блоке try");
            } finally {
                r.close();
            }
        } catch (RuntimeException e) {
            System.out.println("finally: " + e.getMessage()
                    + ", подавленных: " + e.getSuppressed().length);
        }

        // Вариант 2: try-with-resources
        try (FaultyResource r = new FaultyResource()) {
            throw new RuntimeException("ошибка в блоке try");
        } catch (RuntimeException e) {
            System.out.println("try-with-resources: " + e.getMessage());
            for (Throwable s : e.getSuppressed()) {
                System.out.println("  подавлено: " + s.getMessage());
            }
        }
    }
}

// Вывод:
// finally: ошибка в close(), подавленных: 0
// try-with-resources: ошибка в блоке try
//   подавлено: ошибка в close()

В варианте с finally исходное исключение «ошибка в блоке try» потеряно полностью — именно поэтому отладка такого кода превращается в поиск ошибки, которой «нет». Try-with-resources сохраняет обе ошибки.

Где искать подавленное исключение

Отдельно в лог подавленные исключения не попадают, но e.printStackTrace() и большинство логгеров выводят их внутри стек-трейса основного исключения строкой Suppressed: .... Если в логе видна только «верхняя» ошибка, прокрутите стек-трейс ниже.

catch и finally в try-with-resources

Блоки catch и finally в try-with-resources выполняются уже после того, как все ресурсы закрыты. Поэтому в catch нельзя «спасти» данные через ресурс, а исключение, выброшенное при закрытии, тоже можно поймать в этом catch.

Пример 6. Порядок выполнения

// Resource — класс из примера 4
try (Resource r = new Resource("R")) {
    throw new IllegalArgumentException("сбой");
} catch (IllegalArgumentException e) {
    System.out.println("catch: " + e.getMessage());
} finally {
    System.out.println("finally");
}

// Вывод:
// Открыт R
// Закрыт R
// catch: сбой
// finally

Если исключение выброшено при создании ресурса (например, FileNotFoundException в конструкторе FileReader), тело блока try не выполняется, закрывать нечего, и управление сразу переходит в подходящий catch.

Java 9: ресурс, объявленный до try

Начиная с Java 9 в скобках try-with-resources можно указать уже существующую переменную, если она final или effectively final (после инициализации ей ничего не присваивается). В Java 7 и 8 ресурс нужно было обязательно объявлять внутри скобок, даже если объект уже был создан.

// Java 7/8: приходилось заводить новую переменную
public static String readFirstLine(BufferedReader reader) throws IOException {
    try (BufferedReader br = reader) {
        return br.readLine();
    }
}

// Java 9+: достаточно указать переменную
public static String readFirstLine9(BufferedReader reader) throws IOException {
    try (reader) {
        return reader.readLine();
    }
}

Если переменной где-либо в методе присваивается новое значение, компиляция try (reader) завершится ошибкой: переменная перестаёт быть effectively final.

На чём чаще всего ошибаются

Типичные промахи с try-with-resources связаны с областью видимости ресурса, обёртками над потоками и ресурсами, которые закрывать не нужно. Ниже — что происходит в каждом случае и как исправить.

Ошибка Что происходит Как правильно
Присвоить ресурсу новое значение внутри try: br = new BufferedReader(...) Ошибка компиляции: auto-closeable resource br may not be assigned Объявить отдельную переменную или отдельный try-with-resources
Обратиться к ресурсу в catch или finally Ошибка компиляции cannot find symbol: переменная вне области видимости Нужные данные сохранить в переменную, объявленную до try
Обёртка в одном объявлении: new ObjectInputStream(new FileInputStream(f)) Конструктор ObjectInputStream читает заголовок и может выбросить StreamCorruptedException; внутренний FileInputStream не был объявлен ресурсом и остаётся открытым Объявить каждый поток отдельным ресурсом: try (FileInputStream in = new FileInputStream(f); ObjectInputStream ois = new ObjectInputStream(in))
Files.lines(path) или Files.list(dir) без try-with-resources Stream держит открытый дескриптор файла до сборки мусора; при массовой обработке — «Too many open files» try (Stream<String> lines = Files.lines(path)) { ... }
Объявить ресурсом new Scanner(System.in) Закрывается System.in; следующее чтение с консоли выбросит NoSuchElementException Стандартный ввод не закрывать; подробнее о закрытии Scanner — в уроке Класс Scanner
Считать, что try-with-resources ловит исключения Он только закрывает ресурсы; проверяемое IOException без catch или throws не скомпилируется Добавить catch или объявить исключение в throws

Примеры чтения и записи файлов через FileReader, BufferedWriter и PrintWriter в try-with-resources разобраны в уроке Классы символьных потоков.

Часто задаваемые вопросы

Есть ли try-with-resources в Kotlin?

Отдельной конструкции в Kotlin нет — её заменяет функция-расширение use { } для Closeable и AutoCloseable: BufferedReader(FileReader(path)).use { it.readLine() }. Она вызывает close() после выполнения лямбды, в том числе при исключении, и добавляет исключение из close() к основному как подавленное. Для нескольких ресурсов вызовы use вкладывают друг в друга.

Что будет, если ресурс равен null?

Ничего страшного: try (BufferedReader br = null) { } компилируется и выполняется без NullPointerException. По спецификации языка для ресурса со значением null метод close() просто не вызывается. NPE возникнет только при обращении к такому ресурсу внутри блока try.

Нужно ли вызывать flush() перед закрытием writer в try-with-resources?

Нет. Метод close() у BufferedWriter, PrintWriter, BufferedOutputStream сначала сбрасывает буфер, а затем закрывает поток. flush() нужен, только если данные должны попасть в файл до закрытия — например, когда этот же файл сразу читается другим потоком внутри того же блока try.

Что выполнится раньше: close(), catch или finally?

Сначала закрываются все ресурсы (в обратном порядке объявления), затем выполняется catch, последним — finally. Этот вопрос часто задают на собеседованиях; пример с выводом — в разделе catch и finally в try-with-resources.

Можно ли использовать try-with-resources с JDBC?

Да, и это рекомендуемый способ: Connection, Statement, PreparedStatement и ResultSet реализуют AutoCloseable. Обычно их объявляют в одних скобках — try (Connection c = ds.getConnection(); PreparedStatement ps = c.prepareStatement(sql)), и они закрываются в обратном порядке: сначала запрос, потом соединение.

Практика по теме

Закрепите урок на задачах — у каждой есть уровень и оценка времени.

Все задачи модуля →

Комментарии

hmma0723 Jun 14, 2017
If Student is an entity, we cannot use @ElementCollection.

Зарегистрируйтесь или войдите, чтобы иметь возможность оставить комментарий.