| section | 10 |
|---|---|
| name | Исключения |
| type | practice |
Метод Integer.parseInt() бросает NumberFormatException, если строка не является числом. Перехватываем это исключение и обрабатываем корректно.
public class Main {
public static int parseNumber(String input) {
try {
int number = Integer.parseInt(input);
System.out.println("Успешно распознано число: " + number);
return number;
} catch (NumberFormatException e) {
System.out.println("Ошибка: '" + input + "' не является числом.");
System.out.println("Детали: " + e.getMessage());
return -1; // возвращаем значение по умолчанию
}
}
public static void main(String[] args) {
parseNumber("42"); // успех
parseNumber("abc"); // NumberFormatException
parseNumber("3.14"); // NumberFormatException (не целое число)
parseNumber("-100"); // успех
}
}Разбор кода:
Integer.parseInt()— unchecked-исключение, компилятор не требует его обработки, но обработка делает программу надёжнее.e.getMessage()— возвращает описание ошибки от JVM.- Метод возвращает
-1как сигнал об ошибке — вызывающий код может проверить это значение.
Блок finally выполняется всегда: и при успешном выполнении, и при исключении. Это полезно для освобождения ресурсов.
public class ResourceSimulator {
private String name;
private boolean isOpen;
public ResourceSimulator(String name) {
this.name = name;
this.isOpen = false;
}
public void open() {
isOpen = true;
System.out.println("Ресурс '" + name + "' открыт.");
}
public void process(boolean shouldFail) {
if (shouldFail) {
throw new RuntimeException("Ошибка при обработке ресурса '" + name + "'");
}
System.out.println("Ресурс '" + name + "' обработан успешно.");
}
public void close() {
if (isOpen) {
isOpen = false;
System.out.println("Ресурс '" + name + "' закрыт.");
}
}
}
public class Main {
public static void main(String[] args) {
System.out.println("=== Успешный сценарий ===");
ResourceSimulator res1 = new ResourceSimulator("База данных");
try {
res1.open();
res1.process(false); // без ошибки
} catch (RuntimeException e) {
System.out.println("Поймано исключение: " + e.getMessage());
} finally {
res1.close(); // выполняется всегда
}
System.out.println();
System.out.println("=== Сценарий с ошибкой ===");
ResourceSimulator res2 = new ResourceSimulator("Файл");
try {
res2.open();
res2.process(true); // бросает исключение
} catch (RuntimeException e) {
System.out.println("Поймано исключение: " + e.getMessage());
} finally {
res2.close(); // выполняется даже после исключения
}
}
}Разбор кода:
- В обоих сценариях
close()вызывается вfinally— ресурс всегда освобождается. - Без
finallyпри исключенииclose()не был бы вызван, и ресурс остался бы открытым.
Разные типы исключений обрабатываются в отдельных блоках. Порядок важен: конкретные типы — выше, общие — ниже.
public class Main {
public static void riskyOperation(int choice) {
switch (choice) {
case 1 -> {
int result = 10 / 0; // ArithmeticException
}
case 2 -> {
int[] arr = new int[3];
arr[10] = 5; // ArrayIndexOutOfBoundsException
}
case 3 -> {
String s = null;
s.length(); // NullPointerException
}
default -> System.out.println("Операция " + choice + " выполнена без ошибок.");
}
}
public static void main(String[] args) {
for (int i = 0; i <= 4; i++) {
System.out.println("--- Операция " + i + " ---");
try {
riskyOperation(i);
} catch (ArithmeticException e) {
System.out.println("Арифметическая ошибка: " + e.getMessage());
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("Выход за границы массива: " + e.getMessage());
} catch (NullPointerException e) {
System.out.println("Обращение к null-объекту.");
} catch (Exception e) {
// Перехватывает всё остальное
System.out.println("Неизвестная ошибка: " + e.getClass().getSimpleName());
}
}
}
}Разбор кода:
- Блоки
catchпроверяются сверху вниз — выполняется первый подходящий. Exceptionв последнемcatchперехватывает всё, что не поймали выше.- Если поставить
catch (Exception e)первым, остальные блоки никогда не выполнятся.
Собственные исключения делают код выразительнее: по имени исключения сразу понятна причина ошибки.
// Checked-исключение — вызывающий код обязан его обработать
public class InsufficientFundsException extends Exception {
private final double requested;
private final double available;
public InsufficientFundsException(double requested, double available) {
super(String.format("Недостаточно средств: запрошено %.2f руб., доступно %.2f руб.",
requested, available));
this.requested = requested;
this.available = available;
}
public double getRequested() { return requested; }
public double getAvailable() { return available; }
}
public class BankAccount {
private String owner;
private double balance;
public BankAccount(String owner, double initialBalance) {
this.owner = owner;
this.balance = initialBalance;
}
public void deposit(double amount) {
if (amount <= 0) {
throw new IllegalArgumentException("Сумма пополнения должна быть положительной.");
}
balance += amount;
System.out.printf("Пополнение на %.2f руб. Баланс: %.2f руб.%n", amount, balance);
}
public void withdraw(double amount) throws InsufficientFundsException {
if (amount <= 0) {
throw new IllegalArgumentException("Сумма снятия должна быть положительной.");
}
if (amount > balance) {
throw new InsufficientFundsException(amount, balance);
}
balance -= amount;
System.out.printf("Снятие %.2f руб. Баланс: %.2f руб.%n", amount, balance);
}
public double getBalance() { return balance; }
}
public class Main {
public static void main(String[] args) {
BankAccount account = new BankAccount("Иван", 5000.0);
try {
account.deposit(2000.0);
account.withdraw(3000.0); // успех
account.withdraw(10000.0); // InsufficientFundsException
} catch (InsufficientFundsException e) {
System.out.println("Ошибка: " + e.getMessage());
System.out.printf("Не хватает: %.2f руб.%n",
e.getRequested() - e.getAvailable());
} catch (IllegalArgumentException e) {
System.out.println("Некорректный аргумент: " + e.getMessage());
}
}
}Разбор кода:
InsufficientFundsException extends Exception— checked-исключение, компилятор требует его обработки.- Метод
withdraw()объявляетthrows InsufficientFundsException— вызывающий код знает об этом. - Собственные поля
requestedиavailableпозволяют вызывающему коду получить детали ошибки.
❌ Неправильно — пустой catch поглощает исключение:
public class Main {
public static void main(String[] args) {
try {
String s = null;
System.out.println(s.length()); // NullPointerException
} catch (NullPointerException e) {
// Ничего не делаем — исключение "проглочено"!
// Программа продолжает работу, но мы не знаем, что пошло не так.
}
System.out.println("Программа продолжает работу в неопределённом состоянии.");
}
}✅ Правильно — вариант 1: логирование ошибки:
public class Main {
public static void main(String[] args) {
try {
String s = null;
System.out.println(s.length());
} catch (NullPointerException e) {
System.err.println("Ошибка: обращение к null-объекту.");
System.err.println("Стек вызовов: " + e.getMessage());
// В реальном коде здесь был бы вызов logger.error(...)
}
}
}✅ Правильно — вариант 2: повторный бросок с дополнительным контекстом:
public class DataProcessor {
public void process(String data) {
try {
int value = Integer.parseInt(data);
System.out.println("Обработано: " + value);
} catch (NumberFormatException e) {
// Добавляем контекст и перебрасываем
throw new IllegalArgumentException(
"Некорректные данные для обработки: '" + data + "'", e);
}
}
}
public class Main {
public static void main(String[] args) {
DataProcessor processor = new DataProcessor();
try {
processor.process("не число");
} catch (IllegalArgumentException e) {
System.out.println("Ошибка обработки: " + e.getMessage());
System.out.println("Причина: " + e.getCause().getClass().getSimpleName());
}
}
}Объяснение: Пустой catch — одна из самых опасных практик. Исключение возникает, но программа продолжает работу, не зная о проблеме. Минимальная реакция — вывести сообщение об ошибке. В реальных приложениях используется логирование через специальные библиотеки.
На основе примеров выше попробуй:
- Напиши метод
safeDivide(int a, int b), который возвращает результат деления или0при делении на ноль, выводя при этом предупреждение. - Создай класс
AgeValidatorс методомvalidate(int age), который бросаетIllegalArgumentExceptionпри возрасте меньше 0 или больше 150. - Добавь в класс
BankAccountиз примера 4 методtransfer(BankAccount target, double amount), который перебрасываетInsufficientFundsExceptionвызывающему коду.