JavaRush /Курсы /JAVA 25 SELF /Работа с try-with-resources: автоматическое закрытие ресу...

Работа с try-with-resources: автоматическое закрытие ресурсов

JAVA 25 SELF
36 уровень , 4 лекция
Открыта

1. Введение

Работа с файлами в Java (и не только!) — это всегда работа с внешними ресурсами. Когда вы открываете файл, операционная система выделяет для вашей программы «дескриптор» — специальный идентификатор, который позволяет читать и писать в файл. Количество таких дескрипторов ограничено: если не закрывать файлы, программа может быстро «съесть» все доступные ресурсы и начать выбрасывать загадочные ошибки вида "Too many open files".

Более того, если файл не закрыт, он может оставаться заблокированным для других программ. Например, вы открыли файл на запись, забыли его закрыть, и теперь ни вы, ни кто-либо другой не может его изменить или удалить. Такой себе «вечный захват заложников» в мире файловых систем.

Пример из жизни

FileInputStream fis = new FileInputStream("data.txt");
int b = fis.read();
// ... что-то делаем, а потом забыли fis.close()

Если не вызвать метод close(), файл останется «висящим» до завершения работы программы. В больших приложениях это может привести к утечкам ресурсов и даже к падению приложения.

2. Старый способ: finally + close()

До Java 7 классический способ гарантированного закрытия файла выглядел так:

FileInputStream fis = null;
try {
    fis = new FileInputStream("data.txt");
    // читаем файл
    int b = fis.read();
    // ...
} catch (IOException e) {
    System.out.println("Ошибка при чтении файла: " + e.getMessage());
} finally {
    if (fis != null) {
        try {
            fis.close();
        } catch (IOException e) {
            System.out.println("Ошибка при закрытии файла: " + e.getMessage());
        }
    }
}

Минусы такого подхода

  • Легко забыть про блок finally и получить утечку ресурсов.
  • Много лишнего кода, особенно если потоков несколько.
  • Если при закрытии возникает исключение, его тоже нужно ловить отдельно.
  • Код становится громоздким и менее читаемым.

3. Современный подход: try-with-resources

К счастью, в Java 7 появился синтаксис, который решает эти проблемы изящно и автоматически — try-with-resources.

Как это выглядит

try (FileInputStream fis = new FileInputStream("data.txt")) {
    int b = fis.read();
    // работаем с файлом
} catch (IOException e) {
    System.out.println("Ошибка при работе с файлом: " + e.getMessage());
}
// Здесь fis уже закрыт автоматически!

Главная фишка: все ресурсы, объявленные в круглых скобках после try, будут автоматически закрыты после завершения блока — даже если в середине возникло исключение. Не нужно писать finally, не нужно ловить отдельные ошибки закрытия — Java всё сделает за вас.

Какие классы можно использовать в try-with-resources?

Любой класс, который реализует интерфейс AutoCloseable (или старый добрый Closeable). Это почти все стандартные потоки ввода-вывода: FileInputStream, FileOutputStream, BufferedReader, BufferedWriter, Scanner, PrintWriter и многие другие.

4. Синтаксис try-with-resources: детали и примеры

Один ресурс

try (BufferedReader reader = new BufferedReader(new FileReader("input.txt"))) {
    String line = reader.readLine();
    System.out.println(line);
} catch (IOException e) {
    System.out.println("Ошибка: " + e.getMessage());
}
// reader закрыт автоматически!

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

Можно объявлять сразу несколько ресурсов через точку с запятой:

try (
    BufferedReader reader = new BufferedReader(new FileReader("input.txt"));
    BufferedWriter writer = new BufferedWriter(new FileWriter("output.txt"))
) {
    String line;
    while ((line = reader.readLine()) != null) {
        writer.write(line);
        writer.newLine();
    }
} catch (IOException e) {
    System.out.println("Ошибка при копировании: " + e.getMessage());
}
// оба потока закрыты!

Порядок закрытия: ресурсы закрываются в обратном порядке их объявления. Сначала вызовется writer.close(), затем reader.close(). Это важно, если один поток зависит от другого.

Использование с кастомными классами

Если вы пишете свой класс, который работает с ресурсами, просто реализуйте интерфейс AutoCloseable:

class MyResource implements AutoCloseable {
    public void doSomething() {
        System.out.println("Работаем с ресурсом!");
    }

    @Override
    public void close() {
        System.out.println("Ресурс закрыт!");
    }
}

try (MyResource res = new MyResource()) {
    res.doSomething();
}
// После выхода из блока будет выведено: "Ресурс закрыт!"

5. Как это работает: схема

flowchart TD A[Открытие ресурса в try-with-resources] --> B{В блоке try возникло исключение?} B -- Нет --> C[Ресурс закрывается автоматически] B -- Да --> D[Ресурс закрывается автоматически] D --> E[Исключение прокидывается дальше] C --> F[Программа продолжает выполнение]

Вывод: независимо от того, была ли ошибка, ресурс всегда будет закрыт!

6. Примеры: переписываем код «по-новому»

Было (старый стиль):

BufferedReader reader = null;
try {
    reader = new BufferedReader(new FileReader("input.txt"));
    String line = reader.readLine();
    System.out.println(line);
} catch (IOException e) {
    System.out.println("Ошибка: " + e.getMessage());
} finally {
    if (reader != null) {
        try {
            reader.close();
        } catch (IOException e) {
            System.out.println("Ошибка при закрытии: " + e.getMessage());
        }
    }
}

Стало (try-with-resources):

try (BufferedReader reader = new BufferedReader(new FileReader("input.txt"))) {
    String line = reader.readLine();
    System.out.println(line);
} catch (IOException e) {
    System.out.println("Ошибка: " + e.getMessage());
}
// Всё, никакого finally!

7. Что происходит при ошибке во время закрытия?

Иногда сама операция закрытия ресурса может выбросить исключение (например, если файл внезапно исчез). В try-with-resources такие исключения не теряются: если в блоке try уже было одно исключение, а при закрытии ресурса возникло второе, оно будет добавлено как «подавленное» (suppressed exception) к основному. Это можно увидеть с помощью метода Throwable.getSuppressed().

Пример

try (MyResource res = new MyResource()) {
    throw new IOException("Ошибка в блоке try");
} catch (IOException e) {
    System.out.println("Основная ошибка: " + e.getMessage());
    for (Throwable suppressed : e.getSuppressed()) {
        System.out.println("Подавленное исключение: " + suppressed.getMessage());
    }
}

8. Какие классы поддерживают try-with-resources?

Всё просто: любой класс, реализующий интерфейс AutoCloseable. Вот только некоторые из стандартных:

Класс Назначение
FileInputStream
Чтение байтов из файла
FileOutputStream
Запись байтов в файл
FileReader/FileWriter
Чтение/запись текста
BufferedReader/Writer
Буферизация потоков
PrintWriter
Запись текста с форматированием
Scanner
Чтение данных из файла/консоли
ObjectInputStream/Output
Сериализация/десериализация
ZipInputStream/Output
Работа с архивами ZIP
Socket, ServerSocket
Сетевые соединения

Если вы используете сторонние библиотеки — смотрите документацию: если есть метод close(), скорее всего, класс поддерживает try-with-resources.

9. Советы и полезные нюансы

Можно объявлять переменные вне try (с Java 9): можно использовать уже объявленные ресурсы, если они final или «effectively final»:

BufferedReader reader = new BufferedReader(new FileReader("input.txt"));
try (reader) {
    // ...
}

Работает не только с файлами: try-with-resources удобен для любых ресурсов: сетевые соединения, базы данных, любые объекты с методом close().

Не игнорируйте исключения: даже с try-with-resources не забывайте ловить и обрабатывать исключения — это не панацея от всех бед, а просто удобный способ избежать утечек.

Не закрывайте ресурс вручную внутри try: это не нужно — Java всё сделает за вас! Если вызвать close() вручную, а потом закончится блок try, будет попытка закрыть уже закрытый ресурс. Обычно это безопасно, но может сбить с толку.

10. Типичные ошибки при использовании try-with-resources

Ошибка №1: забыли использовать try-with-resources вообще. Если вы всё ещё пишете finally { resource.close(); } — вы либо находитесь в 2011 году, либо не читали эту лекцию! Используйте современный синтаксис.

Ошибка №2: объявили ресурс вне try, а внутри просто используете. Такой код не закроет ресурс автоматически:

BufferedReader reader = new BufferedReader(new FileReader("input.txt"));
try {
    // ... используем reader
} finally {
    // А вот тут забыли закрыть!
}

Ошибка №3: вызываете close() вручную внутри блока try. Это не критично, но избыточно и может привести к двойному закрытию. Просто доверьтесь Java.

Ошибка №4: ловите только Exception, игнорируя IO-специфику. Лучше ловить конкретные исключения (FileNotFoundException, IOException), чтобы давать пользователю понятные сообщения.

Ошибка №5: не обрабатываете подавленные исключения. Если при закрытии ресурса возникла ошибка, она может быть «подавлена». Если вы анализируете ошибки, не забывайте про getSuppressed().

1
Задача
JAVA 25 SELF, 36 уровень, 4 лекция
Недоступна
Закрепление послания в камне с магической печатью ✨
Закрепление послания в камне с магической печатью ✨
1
Задача
JAVA 25 SELF, 36 уровень, 4 лекция
Недоступна
Подсчёт древних манускриптов в библиотеке 📚
Подсчёт древних манускриптов в библиотеке 📚
1
Опрос
Чтение и запись файлов, 36 уровень, 4 лекция
Недоступен
Чтение и запись файлов
Чтение и запись файлов
Комментарии (2)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Артемий Уровень 66
25 октября 2025

        try (Scanner scanner = new Scanner(new File("великий_манускрипт.txt"))) {
            while (scanner.hasNext()) {
                scanner.next();
                wordCount++;
            }
        } catch (FileNotFoundException e) {
            System.out.println("Ошибка: " + e.getMessage());
        }

        // Выводим общее число слов на экран
        System.out.println(wordCount);
    }
Выводит в Output: Ошибка: великий_манускрипт.txt (No such file or directory) 0 Хотя решение засчитано правильным
Alexander Уровень 1
4 ноября 2025
Файл находится в пакете (с заданием), а у вас он указан так, будто лежит в корне проекта.