JavaRush /Курси /Java Syntax Zero /Оператор try with resources

Оператор try with resources

Java Syntax Zero
Рівень 16 , Лекція 1
Відкрита

1. Зовнішні ресурси

Іноді в процесі роботи Java-програма взаємодіє з об'єктами поза Java-машиною. Наприклад, з файлами на диску. Такі об'єкти прийнято називати зовнішніми ресурсами. Внутрішні ресурси — це об'єкти, створені всередині Java-машини.

Зазвичай взаємодія відбувається за такою схемою:

Оператор try with resources

Облік ресурсів

Операційна система веде суворий облік доступних ресурсів, а також контролює спільний доступ різних програм до них. Наприклад, якщо одна програма змінює якийсь файл, інша програма не може змінити (або видалити) цей файл. Це стосується не лише файлів, але на їх прикладі зрозуміліше за все.

В операційної системи є функції (API), які дозволяють програмі захопити який-небудь ресурс і/або звільнити його. Якщо ресурс зайнятий, з ним може працювати лише та програма, яка його захопила. Якщо ресурс вільний, будь-яка програма може захопити його.

Уявіть, що у вас в офісі є спільні кружки. Якщо хтось взяв кружку, інший вже не може взяти її. Але якщо нею скористалися, помили і поставили на місце, її знову може брати хто завгодно. Ну або місця в метро чи в маршрутці. Якщо місце вільне — будь-хто може його зайняти. Якщо місце зайняте — ним розпоряджається той, хто зайняв.

Захоплення зовнішніх ресурсів

Кожного разу, коли ваша Java-програма починає працювати з якимось файлом на диску, Java-машина запитує у операційної системи монопольний доступ до нього. Якщо ресурс вільний, його захоплює Java-машина.

Але після того, як ви закінчили працювати з файлом, цей ресурс (файл) потрібно звільнити: повідомити операційну систему, що він вам більше не потрібен. Якщо ви цього не зробите, ресурс продовжуватиме числитися за вашою програмою.

Для кожної запущеної програми операційна система веде список зайнятих ресурсів. Якщо ваша програма перевищить дозволений їй ліміт ресурсів, нові ресурси операційна система вам вже не дасть.

Хороша новина в тому, що якщо ваша програма завершилася, всі ресурси автоматично звільняються (це робить сама операційна система).

Погана ж новина в тому, що якщо ви пишете серверний додаток (а дуже багато серверних додатків пишуться на Java), ваш сервер повинен працювати днями, тижнями, місяцями без зупинки. І якщо ви на день відкриваєте 100 файлів і не закриваєте їх, через пару тижнів ваш додаток вичерпає свій ліміт і впаде. Не дуже-то схоже на місяці стабільної роботи.


2. Метод close()

У класів, які використовують зовнішні ресурси, є спеціальний метод для їх звільнення — close().

Нижче наведемо приклад програми, яка щось записує в файл і закриває його за собою – звільняє ресурси операційної системи. Виглядає це приблизно так:

Код Примітка
String path = "c:\\projects\\log.txt";
FileOutputStream output = new FileOutputStream(path); output.write(1);
output.close();
Шлях до файлу.
Отримуємо об'єкт файлу: захоплюємо ресурс.
Пишемо у файл
Закриваємо файл — звільняємо ресурс

Після роботи з файлом (або іншим зовнішнім ресурсом) ви маєте викликати у об'єкта, пов'язаного із зовнішнім ресурсом, метод close().

Винятки

Наче все просто. Однак у процесі роботи програми можуть виникнути винятки, і зовнішній ресурс так і не буде звільнено. А це дуже погано.

Щоб метод close() викликався завжди, потрібно обгорнути наш код у блок try-catch-finally і додати метод close() у блок finally. Виглядати це буде приблизно так:

try
{
   FileOutputStream output = new FileOutputStream(path);
   output.write(1);
   output.close();
}
catch (IOException e)
{
   e.printStackTrace();
}
finally
{
   output.close();
}

Цей код не скомпілюється, оскільки змінна output оголошена всередині блоку try {}, а отже, не видима в блоці finally.

Виправляємо:

FileOutputStream output = new FileOutputStream(path);

try
{
   output.write(1);
   output.close();
}
catch (IOException e)
{
   e.printStackTrace();
}
finally
{
   output.close();
}

Добре, але не буде працювати, якщо помилка виникла при створенні об'єкта FileOutputStream, а це може статися дуже легко.

Виправляємо:

FileOutputStream output = null;

try
{
   output = new FileOutputStream(path);
   output.write(1);
   output.close();
}
catch (IOException e)
{
   e.printStackTrace();
}
finally
{
   output.close();
}

Ще є кілька зауважень. По-перше, якщо під час створення об'єкта FileOutputStream виникне помилка, змінна output буде null, і цей факт потрібно враховувати в блоці finally.

По-друге, метод close() викликається в блоці finally завжди, а отже, він не потрібен у блоці try. Фінальний код буде виглядати так:

FileOutputStream output = null;

try
{
   output = new FileOutputStream(path);
   output.write(1);
}
catch (IOException e)
{
   e.printStackTrace();
}
finally
{
   if (output != null)
      output.close();
}

Навіть якщо не враховувати блок catch, який можна опустити, наш код із трьох рядків перетворився на 10. Хоча по суті ми тільки відкрили файл і записали в нього 1. Трохи громіздко, чи не так?


3. try-with-resources

Творці Java і тут вирішили нас побалувати трохи синтаксичним цукром. Починаючи із 7-ї версії Java, у ній з'явився новий оператор try-with-resources (try з ресурсами).

Він створений саме для того, щоб розв'язати проблему з обов'язковим викликом методу close(). У загальному випадку виглядає він досить просто:

try (Клас ім'я = new Клас())
{
     Код, який працює з змінною ім'я
}

Це ще один різновид оператора try. Після ключового слова try потрібно додати круглі дужки, а всередині них — створити об'єкти із зовнішніми ресурсами. Для об'єкта, зазначеного в круглих дужках, компілятор сам додасть секцію finally і виклик методу close().

Нижче написано два еквівалентні приклади:

Довгий код Код з try-with-resources
FileOutputStream output = null;

try
{
   output = new FileOutputStream(path);
   output.write(1);
}
finally
{
   if (output != null)
   output.close();
}
try(FileOutputStream output = new FileOutputStream(path))
{
   output.write(1);
}

Код із використанням try-with-resources значно коротший і легший для читання. А чим менше коду, тим менше шансів зробити друкарську або логічну помилку.

До речі, до оператора try-with-resources можна дописувати блоки catch і finally. А можна і не додавати, якщо в них немає потреби.



4. Кілька змінних одночасно

До речі, часто може виникнути ситуація, коли потрібно відкрити кілька файлів одночасно. Припустимо, ти копіюєш файл, і тобі потрібні два об'єкти: файл, з якого ти копіюєш дані, і файл, у який ти копіюєш дані.

На цей випадок оператор try-with-resources дозволяє створювати в ньому не один об'єкт, а кілька. Код створення об'єктів має розділятися крапкою з комою. Загальний вигляд такої команди:

try (Клас ім'я = new Клас(); Клас2 ім'я2 = new Клас2())
{
   Код, який працює зі змінною ім'я і ім'я2
}

Приклад копіювання файлів:

Довгий код Короткий код
String src = "c:\\projects\\log.txt";
String dest = "c:\\projects\\copy.txt";

FileInputStream input = null;
FileOutputStream output = null;

try
{
   input = new FileInputStream(src);
   output = new FileOutputStream(dest);

   byte[] buffer = input.readAllBytes();
   output.write(buffer);
}
finally
{
   if (input != null)
      input.close();
   if (output != null)
      output.close();
}
String src = "c:\\projects\\log.txt";
String dest = "c:\\projects\\copy.txt";

try(FileInputStream input = new FileInputStream(src);

FileOutputStream output = new FileOutputStream(dest))
{
   byte[] buffer = input.readAllBytes();
   output.write(buffer);
}

Взагалі, що тут скажеш: крута це штука — try-with-resources


5. Інтерфейс AutoCloseable

Але і це ще не все. Уважний читач відразу почне шукати приховані обмеження застосовності даного оператора.

А як працюватиме оператор try-with-resources, якщо у класу немає методу close()? Ну, припустимо, тоді нічого не викличеться. Немає методу, немає проблем.

А як працюватиме try-with-resources, якщо у класу є кілька методів close()? І їм потрібно передавати параметри? І у класу немає методу close() без параметрів?

Сподіваюся, ви дійсно поставили собі ці питання, і можливо не лише їх.

Для того щоб таких питань не було, творці Java вигадали спеціальний клас (інтерфейс) AutoCloseable, у якого лише один метод – close() без параметрів.

А також додали обмеження, що в якості ресурсів у try-with-resources можна передавати лише об'єкти класів, які успадковані від AutoCloseable. Таким чином у цих об'єктів завжди буде метод close() без параметрів.

До речі, як ви думаєте, чи можна в якості ресурсу передати у try-with-resources об'єкт, чий клас має метод close() без параметрів, але який не успадкований від AutoCloseable?

Погана новина: правильна відповідь – ні, класи обов'язково повинні реалізовувати інтерфейс AutoCloseable.

Гарна новина: у Java дуже багато класів реалізовують цей інтерфейс, тож з великою ймовірністю все буде працювати як слід.

Коментарі (5)
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ
Anonymous #3315996 Рівень 3
1 березня 2024
try(Scanner scanner = new Scanner(System.in); BufferedReader bufferedReader = Files.newBufferedReader(Path.of(scanner.nextLine()))) { String line; while ((line = bufferedReader.readLine()) != null) { System.out.println(line); } }
Pavlo Kezin Рівень 23
18 вересня 2023
Спробував тут двоповерховий try, але не розібрався наскільки код рівнозначний...

try (Scanner scanner = new Scanner(System.in)) {
           String fileName = scanner.nextLine();

        try ( BufferedReader bufferedReader = Files.newBufferedReader(Path.of(fileName));) 
Pavlo Kezin Рівень 23
18 вересня 2023
але валідатор пропустив😁
kalkulator¹ Рівень 51
19 листопада 2022
доволі легко, якщо вчишся зразу з двох різних джерел. Я спочатку забиваю тему лекції в ютубі, а потім починаю читати. Так набагато простіше вчитися.
27 червня 2022
После задачи возвращение к истокам наблюдаю такую картину. Валидатор задачу пропускает, а вот компилятор даже на решенных задачах выдает такое. С чем это может быть связано?