Исключения: try-catch-finally
Что такое исключение, checked vs unchecked, try-catch, finally, try-with-resources, создание своих исключений
Что такое исключение?
Исключение (exception) — это объект, который создаётся при возникновении ошибки во время выполнения программы. Это способ Java сказать: «Что-то пошло не так, и я не могу продолжать нормально».
Представьте, что вы едете на работу. Обычно всё идёт гладко. Но вдруг:
- Пробка на дороге — вы опаздываете
- Сломался автобус — нужно искать другой
- Закрыли метро — планы рушатся
Исключения в программировании работают так же: они сигнализируют о проблемах, которые могут произойти, и позволяют программе принять решение, что делать дальше.
Простой пример исключения
public class ExceptionDemo {
public static void main(String[] args) {
int a = 10;
int b = 0;
int result = a / b;
System.out.println(result);
}
}
Вывод программы:
Exception in thread "main" java.lang.ArithmeticException: / by zero
at ExceptionDemo.main(ExceptionDemo.java:4)
Программа упала! Java создала объект ArithmeticException и вывела сообщение об ошибке. Это unchecked исключение — компилятор не заставляет вас его обрабатывать.
Checked vs Unchecked исключения
Все исключения в Java делятся на два типа:
| Характеристика | Checked | Unchecked |
|---|---|---|
| Примеры | IOException, SQLException | NullPointerException, ArithmeticException |
| Когда проверяются | Компилятором | Во время выполнения |
| Обязательно обрабатывать | Да | Нет (но рекомендуется) |
| Иерархия | Exception | RuntimeException |
Пример checked исключения — работа с файлом:
// Это НЕ скомпилируется без try-catch или throws:
import java.io.FileReader;
import java.io.IOException;
public class CheckedDemo {
public static void main(String[] args) {
FileReader reader = new FileReader("file.txt");
}
}
// ОШИБКА КОМПИЛЯЦИИ: Unhandled exception type IOException
Аналогия: Checked exception — это как предупреждение на коробке с лекарством: «Может вызвать сонливость». Вы обязаны это знать и принять меры. Unchecked exception — как солнечный удар: может случиться, но предупредить невозможно.
try-catch
Конструкция try-catch позволяет «поймать» исключение и обработать его, чтобы программа не упала:
public class TryCatchDemo {
public static void main(String[] args) {
try {
int a = 10;
int b = 0;
int result = a / b;
System.out.println(result);
} catch (ArithmeticException e) {
System.out.println("Ошибка деления на ноль!");
System.out.println("Сообщение: " + e.getMessage());
}
System.out.println("Программа продолжает работу");
}
}
Вывод программы:
Ошибка деления на ноль!
Сообщение: / by zero
Программа продолжает работу
Программа не упала! Исключение было «поймано» в блоке catch, и выполнение продолжилось.
Несколько catch-блоков
Вы можете обрабатывать разные типы исключений отдельно:
public class MultiCatchDemo {
public static void main(String[] args) {
try {
String text = null;
System.out.println(text.length());
int[] arr = new int[3];
arr[10] = 5;
} catch (NullPointerException e) {
System.out.println("Ссылка null: " + e.getMessage());
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("Индекс за границей: " + e.getMessage());
} catch (Exception e) {
System.out.println("Другая ошибка: " + e.getMessage());
}
}
}
Вывод программы:
Ссылка null: null
Как только произошло первое исключение (NullPointerException), перешли в соответствующий catch. Второе исключение не было создано.
Важно: Более специфичные исключения должны идти ПЕРЕД общими. Если поставить catch (Exception e) первым — компилятор выдаст ошибку.
multi-catch (Java 7+)
Если обработка для нескольких исключений одинаковая, можно объединить их:
try {
// код, который может выбросить разные исключения
} catch (NullPointerException | ArrayIndexOutOfBoundsException e) {
System.out.println("Ошибка индексации: " + e.getMessage());
}
finally
Блок finally выполняется всегда — было исключение или нет. Он используется для освобождения ресурсов:
public class FinallyDemo {
public static void main(String[] args) {
System.out.println("Начало");
try {
System.out.println("Внутри try");
int result = 10 / 0;
} catch (ArithmeticException e) {
System.out.println("Внутри catch");
} finally {
System.out.println("Внутри finally");
}
System.out.println("Конец");
}
}
Вывод программы:
Начало
Внутри try
Внутри catch
Внутри finally
Конец
Блок finally выполняется даже если:
- В catch произошло новое исключение
- В try/catch есть
return - В catch есть
return
throws и throw
throw — создать и выбросить исключение вручную:
public static int divide(int a, int b) {
if (b == 0) {
throw new ArithmeticException("Деление на 0 запрещено");
}
return a / b;
}
throws — объявить, что метод может выбросить исключение:
// Метод объявляет, что может выбросить IOException
public static void readFile(String path) throws java.io.IOException {
java.io.FileReader reader = new java.io.FileReader(path);
reader.read();
reader.close();
}
// Вызывающий код ОБЯЗАН обработать или пробросить дальше:
public static void main(String[] args) {
try {
readFile("data.txt");
} catch (java.io.IOException e) {
System.out.println("Файл не найден: " + e.getMessage());
}
}
try-with-resources (Java 7+)
Конструкция try-with-resources автоматически закрывает ресурсы (файлы, соединения и т.д.):
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
public class TryWithResourcesDemo {
public static void main(String[] args) {
// Старый способ:
BufferedReader reader = null;
try {
reader = new BufferedReader(new FileReader("file.txt"));
String line = reader.readLine();
System.out.println(line);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (reader != null) reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
// Новый способ (Java 7+):
try (BufferedReader r = new BufferedReader(new FileReader("file.txt"))) {
String line = r.readLine();
System.out.println(line);
} catch (IOException e) {
e.printStackTrace();
}
}
}
Ресурсы в скобках автоматически закрываются вызовом метода close() после завершения блока try.
Аналогия: try-with-resources — как автоматическое закрытие зонтика. Вы его открываете (try), пользуетесь, а потом он сам закрывается (finally). Даже если вас облили водой — зонтик всё равно закроется.
Свои исключения
Вы можете создавать собственные исключения, наследуя от Exception (checked) или RuntimeException (unchecked):
// Custom checked exception
public class InsufficientFundsException extends Exception {
private double deficit;
public InsufficientFundsException(double deficit) {
super("Недостаточно средств. Не хватает: " + deficit);
this.deficit = deficit;
}
public double getDeficit() {
return deficit;
}
}
// Custom unchecked exception
public class InvalidAgeException extends RuntimeException {
public InvalidAgeException(String message) {
super(message);
}
}
// Использование:
public class BankAccount {
private double balance;
public BankAccount(double balance) {
this.balance = balance;
}
public void withdraw(double amount) throws InsufficientFundsException {
if (amount > balance) {
throw new InsufficientFundsException(amount - balance);
}
balance -= amount;
}
public String setAge(int age) {
if (age < 0 || age > 150) {
throw new InvalidAgeException("Невозможный возраст: " + age);
}
return "Возраст установлен: " + age;
}
}
// Обработка:
public class CustomExceptionDemo {
public static void main(String[] args) {
BankAccount account = new BankAccount(1000);
try {
account.withdraw(500);
System.out.println("Снятие успешно");
account.withdraw(600);
} catch (InsufficientFundsException e) {
System.out.println(e.getMessage());
System.out.printf("Не хватает: %.2f%n", e.getDeficit());
}
try {
System.out.println(account.setAge(200));
} catch (InvalidAgeException e) {
System.out.println(e.getMessage());
}
}
}
Вывод программы:
Снятие успешно
Недостаточно средств. Не хватает: 100.0
Не хватает: 100.0
Невозможный возраст: 200
Иерархия исключений
Все исключения наследуют от Throwable:
Throwable
├── Error (серьёзные системные ошибки, не обрабатываются)
│ ├── OutOfMemoryError
│ ├── StackOverflowError
│ └── ...
└── Exception
├── RuntimeException (unchecked)
│ ├── NullPointerException
│ ├── ArrayIndexOutOfBoundsException
│ ├── ArithmeticException
│ ├── IllegalArgumentException
│ └── ...
└── IOException, SQLException и др. (checked)
Практический пример: валидация данных
public class ValidationException extends Exception {
private final String field;
private final String value;
public ValidationException(String field, String value, String message) {
super(message);
this.field = field;
this.value = value;
}
public String getField() { return field; }
public String getValue() { return value; }
}
public class UserValidator {
public static void validateEmail(String email) throws ValidationException {
if (email == null || !email.contains("@")) {
throw new ValidationException("email", email,
"Email должен содержать @");
}
}
public static void validateAge(int age) throws ValidationException {
if (age < 0 || age > 150) {
throw new ValidationException("age", String.valueOf(age),
"Возраст должен быть от 0 до 150");
}
}
public static void validateName(String name) throws ValidationException {
if (name == null || name.trim().isEmpty()) {
throw new ValidationException("name", name,
"Имя не может быть пустым");
}
}
public static void main(String[] args) {
try {
validateName("Алиса");
validateEmail("alice@mail.ru");
validateAge(25);
System.out.println("Все данные корректны!");
} catch (ValidationException e) {
System.out.printf("Ошибка в поле '%s': %s%n",
e.getField(), e.getMessage());
}
}
}
Вывод программы:
Все данные корректны!
Итоги урока
Итоги урока
- Исключение — объект, сигнализирующий об ошибке
- Checked исключения — компилятор требует обработки
- Unchecked исключения (RuntimeException) — обработка не обязательна
- try-catch — перехват и обработка исключений
- finally — блок, выполняемый всегда
- throw — выбросить исключение
- throws — объявить возможные исключения
- try-with-resources — автоматическое закрытие ресурсов
- Свои исключения — наследуйте от Exception или RuntimeException
Следующий урок: Generics →
Тест по исключениям (try-catch-finally)
5 вопросов