Собственные исключения

Базовый Java
≈ 2 мин чтения

Зачем создавать свои исключения

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

Собственное исключение — это обычный класс, который наследуется от подходящего класса исключений. Выбор между Exception и RuntimeException подчиняется тем же правилам checked и unchecked исключений, которые мы уже разобрали.

Создаём собственное исключение

Чаще всего классу исключения не нужна сложная логика. Достаточно дать ему понятное имя и передать сообщение конструктору родительского класса.

public class UserNotFoundException extends RuntimeException {

    public UserNotFoundException(String message) {
        super(message);
    }
}

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

public User findUser(long id) {
    User user = loadUser(id);

    if (user == null) {
        throw new UserNotFoundException("User not found: " + id);
    }

    return user;
}

Код, который вызывает findUser(), получает не абстрактную «ошибку времени выполнения», а конкретный тип ошибки. Его можно отдельно перехватить через catch, если на эту ситуацию нужно отреагировать особым образом.

Checked или unchecked

Собственное исключение не становится checked или unchecked само по себе — это определяется его родителем. Если класс наследуется от Exception, но не от RuntimeException, компилятор требует обработать или объявить такое исключение. Наследник RuntimeException остаётся unchecked.

public class InvalidReportException extends Exception {

    public InvalidReportException(String message) {
        super(message);
    }
}

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

Имя исключения — часть смысла программы. PaymentRejectedException или UserNotFoundException обычно говорят о проблеме больше, чем безликий RuntimeException с тем же текстом сообщения.

Сообщение и причина ошибки

Если новое исключение создаётся поверх уже возникшей ошибки, полезно сохранить исходную причину. Для этого можно добавить конструктор с параметром Throwable cause и передать его в super.

public class UserLoadingException extends RuntimeException {

    public UserLoadingException(String message, Throwable cause) {
        super(message, cause);
    }
}

Так у нового исключения остаётся собственный понятный смысл, но исходная ошибка не теряется: её можно получить через getCause(), а цепочка причин сохраняется в stack trace.