1. Знайомство з блоком finally
Коли ви працюєте з ресурсами — файлами, мережевими з’єднаннями, базами даних — важливо бути впевненими, що їх завжди буде закрито або звільнено завжди, навіть якщо під час роботи сталася помилка. У Java для цього є спеціальний блок — finally.
Як працює finally?
Блок finally — це частина конструкції try-catch-finally. Код усередині finally виконується завжди (якщо він є) — незалежно від того, чи стався виняток. Навіть якщо в try був return або було кинуто виняток — finally усе одно виконається (хіба що вимкнули комп’ютер або програму було примусово завершено через System.exit(0)).
Синтаксис:
try {
// Код, де може виникнути виняток
} catch (ExceptionType e) {
// Обробка помилки
} finally {
// Цей код виконається завжди!
}
Приклад
try {
System.out.println("Початок роботи");
int result = 10 / 0; // тут станеться помилка
System.out.println("Результат: " + result);
} catch (ArithmeticException e) {
System.out.println("Помилка: ділення на нуль");
} finally {
System.out.println("Цей код виконається у будь‑якому разі");
}
Результат виконання:
Початок роботи
Помилка: ділення на нуль
Цей код виконається у будь‑якому разі
Що відбувається?
- У try ви пробуєте поділити два числа й отримуєте виняток.
- Якщо під час ділення стався виняток — catch його перехопить і обробить.
- Але! У будь‑якому разі finally виконається і виведе повідомлення у консоль.
2. finally без catch
Є три варіанти конструкції:
- Повний: try-catch-finally
- Без finally: try-catch
- Без catch: try-finally
Третій варіант використовують, коли перехоплювати й обробляти виняток має метод на вищому рівні. Блок finally потрібен, щоб гарантувати виконання певного коду:
- Закриття файлів, мережевих з’єднань, з’єднань із базою даних.
- Звільнення будь‑яких ресурсів (наприклад, блокувань).
- Логування: запис інформації про завершення операції.
Приклад:
try {
System.out.println("Ділимо числа");
int result = 10 / 0; // помилка!
System.out.println("Результат: " + result);
} finally {
System.out.println("Блок finally виконано");
}
Результат:
Ділимо числа
Блок finally виконано
Exception in thread "main" java.lang.ArithmeticException: / by zero
Коли finally не виконується?
Він практично завжди виконується. Винятки — у таких випадках:
- Програму примусово завершили за допомогою System.exit(0).
- Програмне припинення або примусове завершення потоку, у якому виконується finally.
- Комп’ютер вимкнули або вимкнули живлення.
3. Оператор throw: як згенерувати виняток самостійно
Іноді Java сама «кидає» винятки (наприклад, ділення на нуль або вихід за межі масиву). Але бувають ситуації, коли ви хочете явно вказати: «Це помилка, я не можу продовжувати виконання». Для цього в Java є оператор throw.
Аналогія: якщо ви в магазині бачите прострочений товар — ви подаєте скаргу. Так і в коді: якщо щось не так — ви генеруєте виняток.
Синтаксис оператора throw
throw new ExceptionType("Повідомлення про помилку");
ExceptionType — будь‑який клас, який успадковується від Throwable (зазвичай Exception або RuntimeException). У дужках — повідомлення, яке допоможе зрозуміти, що пішло не так.
Приклад: перевірка аргументів методу
public static int safeDivide(int a, int b) {
if (b == 0) {
throw new IllegalArgumentException("Дільник не може дорівнювати нулю");
}
return a / b;
}
Використання:
public static void main(String[] args) {
try {
int result = safeDivide(10, 0);
System.out.println("Результат: " + result);
} catch (IllegalArgumentException e) {
System.out.println("Помилка: " + e.getMessage());
}
}
Результат:
Помилка: Дільник не може дорівнювати нулю
Коли використовувати throw?
- Перевірка аргументів методу (наприклад, якщо передано null або некоректні дані).
- Перевірка стану об’єкта (наприклад, спроба зняти кошти з рахунку, на якому залишок — 0).
- Всередині catch — якщо потрібно повторно згенерувати виняток або передати його далі (наприклад, щоб додати додаткову інформацію).
4. Поєднання try-catch-finally та throw
Іноді ці конструкції працюють разом. Наприклад, ви перехоплюєте один виняток, а потім вирішуєте кинути власний, більш інформативний виняток.
public static int parseAndDivide(String text, int divisor) {
try {
int number = Integer.parseInt(text);
if (divisor == 0) {
throw new IllegalArgumentException("Дільник не може дорівнювати нулю");
}
return number / divisor;
} catch (NumberFormatException e) {
throw new IllegalArgumentException("Рядок '" + text + "' не є числом");
} finally {
System.out.println("Спроба обробити рядок: " + text);
}
}
Використання:
try {
int result = parseAndDivide("42a", 2);
System.out.println("Результат: " + result);
} catch (IllegalArgumentException e) {
System.out.println("Помилка: " + e.getMessage());
}
Результат:
Спроба обробити рядок: 42a
Помилка: Рядок '42a' не є числом
Важливий нюанс: return і finally
Навіть якщо в блоці try є return, finally усе одно виконається!
public static int getValue() {
try {
return 10;
} finally {
System.out.println("finally усе одно спрацює!");
}
}
Виклик getValue() виведе:
finally усе одно спрацює!
5. Типові помилки під час використання finally та throw
Помилка № 1: забули закрити ресурс без finally.
Дуже поширена проблема: ви відкрили файл, але не закрили його — унаслідок цього може виникнути витік ресурсів. Завжди використовуйте finally (або конструкцію try-with-resources, про яку поговоримо пізніше).
Помилка № 2: згенерували виняток, але не обробили його.
Якщо ви генеруєте виняток за допомогою throw, але його ніде не перехоплюєте (немає try-catch), програма завершиться аварійно. Завжди думайте, хто перехоплюватиме ваш виняток.
Помилка № 3: return у finally.
Якщо ви помилково написали return усередині finally, воно «перебʼє» всі попередні return або throw. Це може призвести до важко виявлюваних помилок. Так робити категорично не рекомендується!
public int tricky() {
try {
return 1;
} finally {
return 2; // НЕБЕЗПЕЧНО: повернеться 2, а не 1!
}
}
Результат: повернеться 2, хоча в try було 1.
Помилка № 4: втрата інформації про виняток.
Якщо ви перехоплюєте один виняток, а потім кидаєте новий, не передавши старий як причину (наприклад, e), ви втрачаєте стек викликів, що ускладнює налагодження. Краще робити так:
catch (NumberFormatException e) {
throw new IllegalArgumentException("Помилка перетворення", e);
}
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ