JavaRush /Java блог /Random UA /Кава-брейк #203. Як обробляти винятки за допомогою операт...

Кава-брейк #203. Як обробляти винятки за допомогою оператора try-with-resource

Стаття з групи Random UA
Джерело: Medium У цьому посібнику описані переваги оператора try-with-resource в порівнянні з try-catch-finally. Також ви дізнаєтеся, за яких умов з'являються пригнічені винятки та як використовувати try-with-resources з кількома ресурсами. Кава-брейк #203.  Як обробляти винятки за допомогою оператора try-with-resource - 1Конструкція try with resources , також відома як try-with-resources , є механізмом обробки винятків Java, який може автоматично закривати такі ресурси, як Java InputStream або JDBC Connection, після завершення роботи з ними. У 2011 році компанія Oracle додала try with resources до синтаксису мови Java, щоб гарантувати, що такі об'єкти, як мережеві сокети, підключення до бази даних та посилання на файли та папки, будуть коректно закриватися після їх використання. Неможливість закрити ці ресурси після того, як розробник відкриває для них дескриптор, може призвести до витоку пам'яті, спрацьовування запобіжних процедур складання сміття та перевантаження процесора на сервері.

До Java 7

У Java, якщо ви використовуєте такі ресурси, як потоки вводу-виводу (input/output streams), вам завжди потрібно закривати їх після використання. Оскільки вони також можуть генерувати винятки, вони повинні знаходитись у блоці try-catch . Закриття має відбуватися в блоці finally . Принаймні так було до Java 7. Але у цього є кілька недоліків:
  • Вам потрібно перевірити, чи не ваш ресурс null , перш ніж закривати його.
  • Саме закриття може генерувати винятки, тому у вашому блоці finally доводиться мати ще один try-catch .
  • Програмісти схильні забувати закривати свої ресурси.

Як використовувати оператор try-with-resource?

Спочатку цей оператор представабо в Java 7 і його ідея полягала в тому, що розробникам тепер не потрібно турбуватися про управління ресурсами, які вони використовують у блоці try-catch-finally . Це досягається за рахунок усунення необхідності в блоках finally , які на практиці використовувалися розробниками лише для закриття ресурсів. Java оператор try-with-resources — це оператор try , який оголошує один або кілька ресурсів. Ресурс є об'єктом, який необхідно закрити після завершення програми. Коли виконання коду залишає блок try-with-resources , будь-який ресурс, відкритий в блоці try-with-resources , автоматично закривається, незалежно від того, чи з'являються якісь винятки або всередині блоку try-with-resources , або при спробі закрити ресурси. Щоб використовувати мовну функцію Java try-with-resources , застосовуються такі правила:
  • Всі об'єкти, керовані оператором try-with-resources , повинні реалізовувати інтерфейс AutoCloseable .
  • У блоці try-with-resources можна створити кілька об'єктів AutoCloseable .
  • Об'єкти, оголошені в операторі try-with-resources виконуються в блоці try , але не в блоках catch і finally .
  • Метод close() об'єктів, оголошених у блоці try-with-resources викликається незалежно від того, чи з'являється виняток під час виконання.
  • Якщо у методі close() генерується виняток, він може бути класифікований як пригнічений виняток (suppressed exception).
Блоки catch і finally, як і раніше, можна використовувати в блоці try-with-resource , і вони будуть працювати так само, як і в звичайному try . Ресурс, на який посилається об'єкт AutoCloseable завжди буде закритий, якщо використовується try-with-resource . Так усуваються потенційні витоку пам'яті, зазвичай викликані неправильним розподілом ресурсів.

Синтаксис

try(declare resources here) {
    // использовать ресурсы
}
catch(FileNotFoundException e) {
    // обработка исключений
}

Практичне використання try-with-resource

Для автоматичного закриття ресурс повинен бути оголошений та ініціалізований усередині try :
try (PrintWriter writer = new PrintWriter(new File("test.txt"))) {
    writer.println("Hello World");
}

Заміна try-catch-finally на try-with-resources

Простий та очевидний спосіб використання функціональності try-with-resources – це заміна традиційного та багатослівного блоку try-catch-finally . Давайте порівняємо такі приклади коду. Перший приклад - це типовий блок try-catch-finally :
Scanner scanner = null;
try {
    scanner = new Scanner(new File("test.txt"));
    while (scanner.hasNext()) {
        System.out.println(scanner.nextLine());
    }
} catch (FileNotFoundException e) {
    e.printStackTrace();
} finally {
    if (scanner != null) {
        scanner.close();
    }
}
А ось нове супер лаконічне рішення з використанням try-with-resources :
try (Scanner scanner = new Scanner(new File("test.txt"))) {
    while (scanner.hasNext()) {
        System.out.println(scanner.nextLine());
    }
} catch (FileNotFoundException fnfe) {
    fnfe.printStackTrace();
}

Різниця між try та try-with-resource

Коли справа доходить до винятків, існує різниця між блоком try-catch-finally та блоком try-with-resources . Виняток видається як і блоці try , і у блоці finally , проте метод повертає виняток, згенероване лише у блоці finally . Для try-with-resources , якщо у блоці try і операторі try-with-resources виникає виняток, метод повертає виняток, створене блоці try . Винятки, що генеруються блоком try-with-resources , пригнічуються, тобто ми можемо сказати, що блок try-with-resources генерує пригнічені винятки.

Чому я маю використовувати оператор try-with-resource?

Оператор try-with-resources гарантує, що кожен ресурс буде закрито наприкінці виконання оператора. Якщо ми не закриємо ресурси, це може призвести до витоку ресурсів і програма може вичерпати доступні ресурси. Так відбувається, коли ви використовуєте блок try-catch-finally . До Java SE 7 ви могли використовувати блок finally , щоб гарантувати, що ресурс буде закритий незалежно від того, як завершує роботу оператор try : нормально чи раптово. У цьому прикладі замість оператора try-with-resources використовується блок finally :
static String readFirstLineFromFileWithFinallyBlock(String path) throws IOException {

    FileReader fr = new FileReader(path);
    BufferedReader br = new BufferedReader(fr);
    try {
        return br.readLine();
    } finally {
        br.close();
        fr.close();
    }
}
У цьому прикладі може бути витік ресурсів. Програма повинна робити більше, ніж покладатися на збирач сміття, щоб звільнити пам'ять ресурсу після завершення роботи з ним. Програма також повинна повернути ресурс назад в операційну систему, зазвичай, за допомогою виклику методу ресурсу close. Однак, якщо програма не зробить цього до того, як збирач сміття поверне ресурс, то інформація, необхідна для звільнення ресурсу, буде втрачена. Відбувається витік ресурсу, який операційна система досі вважає за використовуване. У наведеному вище прикладі, якщо метод readLine видає виняток, і оператор br.close() в блоці finally видає виняток, то стався витік FileReader . Саме тому вам краще використовувати оператор try-with-resources замість блоку finally , щоб закрити ресурси вашої програми.

Ще один приклад

У наступному прикладі зчитується перший рядок із файлу. Для читання даних використовуються екземпляри FileReader та BufferedReader . Це ресурси, які потрібно закрити після завершення роботи програми.
import java.io.*;
class Main {
  public static void main(String[] args) {
    String line;
    try(BufferedReader br = new BufferedReader(new FileReader("test.txt"))) {
      while ((line = br.readLine()) != null) {
        System.out.println("Line =>"+line);
      }
    } catch (IOException e) {
      System.out.println("IOException in try block =>" + e.getMessage());
    }
  }
}
Як бачите, ресурсом, оголошеним в операторі try-with-resources є BufferedReader . Оператори оголошення цього ресурсу з'являються у круглих дужках відразу після ключового слова try . Класи BufferedReader в Java SE 7 і пізніших версіях реалізують інтерфейс java.lang.AutoCloseable . Оскільки екземпляри BufferedReader оголошені в операторі try-with-resource , вони будуть закриті незалежно від того, чи завершить роботу оператор try нормально або раптово (якщо метод BufferedReader.readLine() згенерував виняток IOException ).

Пригнічені винятки

Якщо блок try видає виняток, і один або кілька винятків з'являється в блоці try-with-resources , то винятки, створювані блоком try-with-resources , пригнічуються. Іншими словами, ми можемо сказати, що винятки, що генеруються try-with-resources , є пригніченими винятками. Ви можете отримати ці винятки за допомогою методу getSuppress() класу Throwable . У наведеному раніше прикладі виключення були викликані оператором try-with-resources , за наступних умов:
  • Не знайдено файл test.txt .
  • Закриття об'єкта BufferedReader .
Виняток також може бути викинуто з блоку try , оскільки читання файлу може завершитися помилкою з багатьох причин у будь-який час. Якщо винятки викидаються як із блоку try , і з оператора try-with-resources , то першому випадку виникає виняток, тоді як у другому випадку виняток пригнічується.

Отримання пригнічених винятків

У Java 7 і пізніших версіях пригнічені винятки можна отримати, викликавши метод Throwable.getSuppressed() з винятку, створеного блоком try . getSuppress() повертає масив, що містить усі винятки, які були придушені оператором try-with-resources . Якщо ніяких винятків не було пригнічено або придушення вимкнено, повертається порожній масив. Перед вами приклад отримання пригнічених винятків у блоці catch :
catch(IOException e) {
  System.out.println("Thrown exception=>" + e.getMessage());
  Throwable[] suppressedExceptions = e.getSuppressed();
  for (int i=0; i" + suppressedExceptions[i]);
  }
}

Переваги використання try-with-resources

  • Простий і простий у написанні код.
  • Автоматичне керування ресурсами.
  • Кількість рядків коду зменшено.
  • Коли кілька ресурсів відкриваються в try-with-resources , вони закриваються у зворотному порядку, щоб уникнути проблем із залежностями.
І звичайно ж, тепер для закриття ресурсу не потрібно блоку finally . Раніше, до Java 7 нам доводилося використовувати блок finally , щоб переконатися, що ресурс закритий, щоб уникнути витоків ресурсів. Ось програма, схожа на перший приклад. У цій програмі ми використовували блок для закриття ресурсів.
import java.io.*;
class Main {
  public static void main(String[] args) {
    BufferedReader br = null;
    String line;
    try {
      System.out.println("Entering try block");
      br = new BufferedReader(new FileReader("test.txt"));
      while ((line = br.readLine()) != null) {
        System.out.println("Line =>"+line);
      }
    } catch (IOException e) {
      System.out.println("IOException in try block =>" + e.getMessage());
    } finally {
      System.out.println("Entering finally block");
      try {
        if (br != null) {
          br.close();
        }
      } catch (IOException e) {
        System.out.println("IOException in finally block =>"+e.getMessage());
      }
    }
  }
}
Висновок:
Entering try block Line =>line from test.txt file Entering finally block
Як видно з наведеного прикладу, використання блоку finally для очищення ресурсів ускладнює код. Звернули увагу на блок try...catch у блоці finally ? Це пов'язано з тим, що виняток IOException також може виникнути при закритті екземпляра BufferedReader всередині блоку finally , тому воно також перехоплюється та обробляється. Оператор try-with-resources виконує автоматичне керування ресурсами. Нам не потрібно закривати ресурси, оскільки JVM автоматично їх закриває. Це робить код більш читабельним та легким для написання.

Try-with-resources з кількома ресурсами

Ми можемо оголосити кілька ресурсів у блоці try-with-resources , просто розділивши їх крапкою з комою:
try (Scanner scanner = new Scanner(new File("testRead.txt"));
    PrintWriter writer = new PrintWriter(new File("testWrite.txt"))) {
    while (scanner.hasNext()) {
	writer.print(scanner.nextLine());
    }
}

Java 9 - Effectively Final Variables

До Java 9 ми могли використовувати свіжі змінні тільки всередині блоку try-with-resources :
try (Scanner scanner = new Scanner(new File("testRead.txt"));
    PrintWriter writer = new PrintWriter(new File("testWrite.txt"))) {
    // omitted
}
Зауважте, це досить багатослівно при оголошенні кількох ресурсів. Починаючи з Java 9 (оновлення JEP 213), ми можемо використовувати змінні final або навіть effectively final всередині блоку try-with-resources :
final Scanner scanner = new Scanner(new File("testRead.txt"));
PrintWriter writer = new PrintWriter(new File("testWrite.txt"))
try (scanner;writer) {
    // omitted
}
Простіше кажучи, змінна фактично є final , якщо вона не змінюється після першого присвоєння, навіть якщо вона явно не позначена як final . Як показано вище, змінна scanner явно оголошена як final , тому ми можемо використовувати її з блоком try-with-resources . Хоча змінна writer не є явно final , вона не змінюється після першого присвоєння. Таким чином, ми можемо також використовувати змінну writer . Сподіваюся, сьогодні ви отримали найкраще уявлення про те, як обробляти винятки за допомогою оператора try-with-resource . Приємного навчання!
Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ