JavaRush /Курси /JAVA 25 SELF /Винятки як частина API й try‑with‑resources

Винятки як частина API й try‑with‑resources

JAVA 25 SELF
Рівень 24 , Лекція 4
Відкрита

1. Винятки як частина API

Чому винятки — це частина «контракту» методу?

Коли ви пишете метод, ви визначаєте не лише параметри й повернене значення, а й які винятки він може викидати. Це частина «контракту» між вашим методом і тими, хто його використовуватиме. Якщо метод може викинути виняток, про це мають знати всі, хто його викликає, — щоб коректно обробити помилку (або принаймні не дивуватися, коли програма раптово завершиться з помилкою).

Приклад:

public void readFile(String filename) throws IOException {
    // ... читання файлу
}

Тут явно зазначено, що метод може викинути IOException. Це сигнал для користувача методу: «Будьте готові обробити помилку читання файлу!»

Документування винятків: анотація @throws у Javadoc

Щоб інші розробники розуміли, які винятки може викидати ваш метод, використовуйте анотацію @throws (або @exception) у Javadoc.

Приклад:

/**
 * Зчитує вміст файлу.
 *
 * @param filename імʼя файлу
 * @return вміст файлу у вигляді рядка
 * @throws IOException якщо сталася помилка читання файлу
 */
public String readFile(String filename) throws IOException {
    // ...
}

Навіщо це потрібно?

  • Допомагає іншим розробникам зрозуміти, які помилки потрібно обробляти.
  • IDE та генератори документації (наприклад, Javadoc) показують ці винятки прямо у підказках.
  • Підвищує надійність і передбачуваність коду.

Checked і unchecked‑винятки в API

Checked‑винятки (нащадки Exception, але не RuntimeException) — частина контракту методу. Їх потрібно або обробити, або явно оголосити у сигнатурі (throws).

Unchecked‑винятки (RuntimeException і нащадки) зазвичай сигналізують про програмні помилки (наприклад, NullPointerException, IllegalArgumentException). Їх не обов’язково вказувати у сигнатурі, але якщо ваш метод може викинути таку помилку (наприклад, через некоректні аргументи), її також варто описати у Javadoc.

Приклад:

/**
 * Ділить a на b.
 * @param a ділене
 * @param b дільник
 * @return результат ділення
 * @throws IllegalArgumentException якщо b == 0
 */
public int divide(int a, int b) {
    if (b == 0) throw new IllegalArgumentException("Дільник не може бути нулем");
    return a / b;
}

Винятки та проєктування API

  • Думайте про користувача вашого методу: які помилки він може й повинен обробити? Які — це баги, а які — «робочі» ситуації?
  • Не зловживайте checked‑винятками: якщо помилка — це баг (наприклад, некоректний аргумент), краще викидати unchecked‑виняток.
  • Документуйте всі винятки, які можуть передаватися «вгору».

2. Конструкція try‑with‑resources

Проблема: як безпечно закривати ресурси?

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

Раніше:

BufferedReader reader = null;
try {
    reader = new BufferedReader(new FileReader("data.txt"));
    String line = reader.readLine();
    // ...
} catch (IOException e) {
    // обробка помилки
} finally {
    if (reader != null) {
        try {
            reader.close();
        } catch (IOException e) {
            // обробка помилки під час закриття
        }
    }
}

Коду багато: легко помилитися й забути закрити ресурс.

Рішення: try‑with‑resources

Починаючи з Java 7 з’явилася конструкція try‑with‑resources, яка автоматично закриває всі ресурси, навіть якщо у блоці виник виняток.

Синтаксис:

try (ResourceType resource = new ResourceType(...)) {
    // робота з ресурсом
} catch (ExceptionType e) {
    // обробка помилки
}

Приклад:

try (BufferedReader reader = new BufferedReader(new FileReader("data.txt"))) {
    String line = reader.readLine();
    System.out.println(line);
} catch (IOException e) {
    System.out.println("Помилка під час читання файлу: " + e.getMessage());
}
// reader.close() буде викликано автоматично!

Як це працює?

  • У дужках після try оголошуються ресурси, які потрібно закрити.
  • Після виходу з блоку try (навіть якщо виник виняток!) для кожного ресурсу викликається метод close().
  • Це працює лише для ресурсів, що реалізують інтерфейс AutoCloseable (або його нащадка Closeable).

Інтерфейс AutoCloseable:

public interface AutoCloseable {
    void close() throws Exception;
}

Усі стандартні ресурси Java (файли, потоки, з’єднання з БД) реалізують цей інтерфейс.

Можна оголошувати кілька ресурсів

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();
    }
}

Обидва ресурси будуть закриті автоматично, навіть якщо виникне виняток.

Переваги try‑with‑resources

  • Безпека: ресурси завжди закриваються, навіть у разі помилок.
  • Стислість: менше коду, менше шансів помилитися.
  • Читабельність: одразу видно, які ресурси використовуються і коли вони закриваються.

3. Практика: пишемо безпечний код із try‑with‑resources

Приклад: читання файлу

public static void printFirstLine(String filename) {
    try (BufferedReader reader = new BufferedReader(new FileReader(filename))) {
        String line = reader.readLine();
        System.out.println("Перший рядок: " + line);
    } catch (IOException e) {
        System.out.println("Помилка: " + e.getMessage());
    }
}

Приклад: запис у файл

public static void writeToFile(String filename, String text) {
    try (BufferedWriter writer = new BufferedWriter(new FileWriter(filename))) {
        writer.write(text);
    } catch (IOException e) {
        System.out.println("Помилка під час запису: " + e.getMessage());
    }
}

Приклад: власний ресурс

Якщо ви пишете свій клас, який потрібно закривати, просто реалізуйте AutoCloseable:

public class MyResource implements AutoCloseable {
    @Override
    public void close() {
        System.out.println("Ресурс закрито!");
    }
}

Тепер його можна використовувати у try‑with‑resources:

try (MyResource res = new MyResource()) {
    // робота з ресурсом
} 

4. Типові помилки та найкращі практики

Помилка № 1: забули закрити ресурс (без try‑with‑resources).
Якщо не використовувати try‑with‑resources, легко забути закрити файл або потік — це призводить до витоків ресурсів.

Помилка № 2: спроба використовувати try‑with‑resources з об’єктом, який не реалізує AutoCloseable.
Якщо ваш клас не реалізує цей інтерфейс, компілятор не дозволить використовувати його в try‑with‑resources.

Помилка № 3: ви не документуєте винятки в API.
Якщо ваш метод може викинути виняток — обов’язково вказуйте це у сигнатурі (throws) і у Javadoc (@throws). Це допоможе іншим правильно використовувати ваш код.

Помилка № 4: перехоплюєте Exception замість конкретних помилок.
Краще перехоплювати лише ті винятки, які справді очікуєте й умієте обробляти.

1
Опитування
Ієрархія винятків, рівень 24, лекція 4
Недоступний
Ієрархія винятків
Поглиблена робота з винятками
Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ