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
Опитування
Читання і запис файлів, рівень 36, лекція 4
Недоступний
Читання і запис файлів
Читання і запис файлів
Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ