JavaRush /Курсы /JAVA 25 SELF /Создание собственных исключений

Создание собственных исключений

JAVA 25 SELF
24 уровень , 1 лекция
Открыта

1. Введение

В стандартной библиотеке Java уже есть множество исключений: NullPointerException, IllegalArgumentException, IOException и другие. Но иногда стандартных исключений недостаточно, чтобы чётко и понятно описать ошибку, возникшую в вашей программе.

Пример из жизни:
Вы пишете банковское приложение. Пользователь пытается снять больше денег, чем есть на счёте. Можно выбросить IllegalArgumentException. Но будет гораздо понятнее, если предложить своё исключение, например, InsufficientFundsException. Тогда по коду сразу видно, что произошло.

Собственные исключения — это такая правильная кастомизация приложения. Они позволяют тонко настроить работу с проблемами, плюс по их названию (если назвать логично!) сразу ясно, что произошло. Кроме того, им присуща самодокументируемость: методы с throws MyException в сигнатуре сразу говорят, какие ошибки могут возникнуть. А ещё — всегда можно добавить дополнительные поля (например, баланс, сумму операции и т. д.).

2. Как создать своё исключение?

Всё очень просто: создайте новый класс, который наследуется от одного из стандартных классов исключений.

  • Для проверяемых (checked) исключений — наследуйтесь от Exception.
  • Для непроверяемых (unchecked) — от RuntimeException.

Пример: проверяемое исключение

public class InvalidCredentialsException extends Exception {
    public InvalidCredentialsException(String message) {
        super(message); // Передаём сообщение в родительский класс
    }
}

Теперь вы можете выбрасывать это исключение в своём коде:

if (!login.equals("admin") || !password.equals("1234")) {
    throw new InvalidCredentialsException("Неверный логин или пароль");
}

Пример: непроверяемое исключение

public class NegativeBalanceException extends RuntimeException {
    public NegativeBalanceException(String message) {
        super(message);
    }
}

Когда использовать checked, а когда unchecked?

  • Checked — если ошибка ожидаема и её можно обработать (например, ошибка валидации, отсутствие файла, неправильные данные пользователя).
  • Unchecked — если ошибка связана с багом в логике программы (например, деление на ноль, нарушение инварианта).

3. Конструкторы: как сделать исключение информативным

Обычно в своём классе исключения реализуют хотя бы один конструктор с параметром String message. Но часто добавляют и другие:

public class ScoreLimitExceededException extends Exception {
    public ScoreLimitExceededException() {
        super();
    }
    public ScoreLimitExceededException(String message) {
        super(message);
    }
    public ScoreLimitExceededException(String message, Throwable cause) {
        super(message, cause);
    }
    public ScoreLimitExceededException(Throwable cause) {
        super(cause);
    }
}

Пояснение:

  • message — текстовое описание ошибки.
  • cause — причина (другое исключение), если вы хотите «обернуть» одну ошибку в другую.

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

4. Использование собственных исключений в коде

Давайте рассмотрим пример: у нас есть пользователь, у пользователя есть баллы, и нельзя добавить больше 100.

public class User {
    private String name;
    private int score;

    public User(String name) {
        this.name = name;
        this.score = 0;
    }

    public void addScore(int points) throws ScoreLimitExceededException {
        if (score + points > 100) {
            throw new ScoreLimitExceededException("Превышен лимит баллов! Попытка добавить: " + points);
        }
        this.score += points;
    }
}

Класс исключения:

public class ScoreLimitExceededException extends Exception {
    public ScoreLimitExceededException(String message) {
        super(message);
    }
}

Обработка:

try {
    user.addScore(60);
    user.addScore(50); // Здесь будет выброшено исключение!
} catch (ScoreLimitExceededException e) {
    System.out.println("Ошибка: " + e.getMessage());
}

Результат:

Ошибка: Превышен лимит баллов! Попытка добавить: 50

У вас наверняка мог возникнуть вопрос: а что, если вместо исключения просто использовать условие if и, например, возвращать false или другое специальное значение, чтобы показать, что операция не удалась? Например, так:

public boolean addScore(int points) {
    if (score + points > 100) {
        return false; // Или выбрасывать какой-нибудь RuntimeException, если нет желания обрабатывать
    }
    this.score += points;
    return true;
}

Хотя такой подход может показаться проще, он имеет недостатки, когда речь идет о серьезных ошибках или нарушении логики приложения.

Во‑первых, возврат false или другого значения для обозначения ошибки обязывает вызывающий код всегда проверять возвращаемое значение. Если программист забудет это сделать, ошибка может остаться незамеченной, что приведет к непредсказуемому поведению программы. Исключения же, напротив, форсируют обработку (для проверяемых исключений) или, по крайней мере, явно сигнализируют о проблеме, если они не перехвачены.

Во‑вторых, исключения более явно передают семантику ошибки. Возвращение false может означать что угодно: «не удалось», «неприменимо», «недоступно». Исключение ScoreLimitExceededException чётко и однозначно говорит: «лимит баллов превышен». Это улучшает читаемость и поддерживаемость кода.

В‑третьих, исключения позволяют централизовать обработку ошибок. Вместо разбрасывания if-проверок по всему коду, где вызывается addScore, вы можете перехватить исключение в одном месте и принять соответствующее решение: вывести сообщение пользователю, записать в лог или выполнить откат транзакции.

Наконец, в случае таких проблем, как превышение лимита, это действительно исключительная ситуация (отсюда и название). Обычный поток выполнения программы предполагает, что баллы будут успешно добавлены. Если это не так — это нарушение бизнес-логики или инвариантов объекта, что и является идеальным сценарием для использования исключений.

5. Добавление собственных полей в исключения

Иногда полезно добавить в своё исключение дополнительные данные, которые помогут обработать ошибку.

Пример:

public class ScoreLimitExceededException extends Exception {
    private int currentScore;
    private int attemptedAdd;

    public ScoreLimitExceededException(String message, int currentScore, int attemptedAdd) {
        super(message);
        this.currentScore = currentScore;
        this.attemptedAdd = attemptedAdd;
    }

    public int getCurrentScore() { 
        return currentScore; 
    }
    public int getAttemptedAdd() { 
        return attemptedAdd; 
    }
}

Использование:

if (score + points > 100) {
    throw new ScoreLimitExceededException(
        "Превышен лимит баллов!",
        this.score,
        points
    );
}

6. Полезные нюансы

Как называть свои исключения?

В Java принято называть пользовательские исключения с суффиксом Exception: InvalidUserInputException, InsufficientFundsException, ScoreLimitExceededException.

Не называйте свои исключения просто Error или Warning — это может ввести в заблуждение других разработчиков (и даже вас самих через пару недель).

Где и когда бросать свои исключения?

  • При валидации данных пользователя (например, пустое имя, отрицательный возраст).
  • При нарушении бизнес-правил (например, превышение лимита, попытка снять больше денег, чем есть на счёте).
  • При ошибках в работе с внешними сервисами (например, сервис недоступен, превышено время ожидания).

7. Типичные ошибки при создании собственных исключений

Ошибка № 1: Наследование не от того класса.
Наследуйтесь от Exception (или RuntimeException), а не от Throwable или Error.

Ошибка № 2: Не добавлен конструктор с сообщением.
Без конструктора, принимающего строку (String message), ваши исключения будут немыми, и их будет сложно отлаживать.

Ошибка № 3: Использование стандартных исключений для бизнес-логики.
Не стоит бросать NullPointerException или IllegalArgumentException там, где нужно своё «говорящее» исключение.

Ошибка № 4: Злоупотребление собственными исключениями.
Не стоит создавать отдельный класс исключения для каждой мелочи. Если ошибка не уникальна для вашей предметной области, используйте стандартные исключения.

Ошибка № 5: Отсутствие сериализации (редко, но бывает).
Если ваше исключение будет передаваться по сети или сохраняться, стоит реализовать implements Serializable. Однако для простых приложений это не критично.

1
Задача
JAVA 25 SELF, 24 уровень, 1 лекция
Недоступна
Страж ворот игры: Проверка игрового счёта
Страж ворот игры: Проверка игрового счёта
1
Задача
JAVA 25 SELF, 24 уровень, 1 лекция
Недоступна
Пароль-крепость: Защита данных пользователя
Пароль-крепость: Защита данных пользователя
1
Задача
JAVA 25 SELF, 24 уровень, 1 лекция
Недоступна
Возрастной контроль в парке аттракционов: Гибкие сообщения об ошибках
Возрастной контроль в парке аттракционов: Гибкие сообщения об ошибках
1
Задача
JAVA 25 SELF, 24 уровень, 1 лекция
Недоступна
Управление складом магических артефактов: Точные детали переполнения
Управление складом магических артефактов: Точные детали переполнения
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ