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
Может быть 3 варианта конструкции:
- Полный: 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);
}
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ