Skip to content

Latest commit

 

History

History
317 lines (265 loc) · 13.3 KB

File metadata and controls

317 lines (265 loc) · 13.3 KB
section 10
name Исключения
type practice

10. Исключения — Практика

Пример 1: try/catch для NumberFormatException при парсинге строки

Метод 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 как сигнал об ошибке — вызывающий код может проверить это значение.

Пример 2: finally — гарантированное выполнение кода

Блок 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() не был бы вызван, и ресурс остался бы открытым.

Пример 3: Несколько блоков catch

Разные типы исключений обрабатываются в отдельных блоках. Порядок важен: конкретные типы — выше, общие — ниже.

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) первым, остальные блоки никогда не выполнятся.

Пример 4: Создание и бросок собственного исключения InsufficientFundsException

Собственные исключения делают код выразительнее: по имени исключения сразу понятна причина ошибки.

// 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 позволяют вызывающему коду получить детали ошибки.

Пример 5: Пустой catch и правильная альтернатива

Неправильно — пустой 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 — одна из самых опасных практик. Исключение возникает, но программа продолжает работу, не зная о проблеме. Минимальная реакция — вывести сообщение об ошибке. В реальных приложениях используется логирование через специальные библиотеки.


Попробуй сам

На основе примеров выше попробуй:

  1. Напиши метод safeDivide(int a, int b), который возвращает результат деления или 0 при делении на ноль, выводя при этом предупреждение.
  2. Создай класс AgeValidator с методом validate(int age), который бросает IllegalArgumentException при возрасте меньше 0 или больше 150.
  3. Добавь в класс BankAccount из примера 4 метод transfer(BankAccount target, double amount), который перебрасывает InsufficientFundsException вызывающему коду.

← Теория | Оглавление | Задания →