Рубрики
Без рубрики

Как создавать пользовательские исключения в Java

Автор оригинала: Dhananjay Singh.

Обзор

В этой статье мы рассмотрим процесс создания пользовательских как проверенных, так и непроверенных исключений в Java.

Если вы хотите узнать больше об исключениях и обработке исключений в Java, мы подробно рассмотрели это в разделе Обработка исключений в Java: Полное руководство с лучшими и наихудшими практиками

Зачем Использовать Пользовательские Исключения?

Хотя исключения Java, как они есть, охватывают почти все исключительные случаи и условия, ваше приложение может создать специальное пользовательское исключение, уникальное для вашего кода и логики.

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

Они помогают клиентам приложений лучше понять, что пошло не так. Они особенно полезны для обработки исключений для API REST , поскольку различные ограничения бизнес-логики требуют, чтобы клиенту отправлялись разные коды ответов.

Если определение пользовательского исключения не дает преимуществ по сравнению с использованием обычного исключения в Java, нет необходимости определять пользовательские исключения, и вам следует просто придерживаться тех, которые уже доступны – нет необходимости снова изобретать горячую воду.

Исключение С Пользовательской Проверкой

Давайте рассмотрим сценарий, в котором мы хотим проверить электронное письмо, переданное в качестве аргумента методу.

Мы хотим проверить, действительно ли это или нет. Теперь мы могли бы использовать встроенный в Java Исключение IllegalArgumentException , что нормально, если мы просто проверяем одну вещь, например, соответствует ли она предопределенному регулярному выражению или нет.

Но предположим, что у нас также есть бизнес-условие, чтобы проверить, что все электронные письма в нашей системе должны быть уникальными. Теперь мы должны выполнить вторую проверку (вызов БД/сети). Мы можем, конечно, использовать то же самое Исключение IllegalArgumentException , но будет неясно, какова точная причина – не прошла ли проверка регулярного выражения по электронной почте или электронное письмо уже существует в базе данных.

Давайте создадим пользовательское исключение, чтобы справиться с этой ситуацией. Чтобы создать исключение, как и любое другое исключение, мы должны расширить java.lang.Исключение класс:

public class EmailNotUniqueException extends Exception {

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

Обратите внимание, что мы предоставили конструктор, который принимает Строку сообщение об ошибке и вызывает конструктор родительского класса. Теперь это не обязательно, но это обычная практика, чтобы иметь какие-то подробности о произошедшем исключении.

Вызывая super(сообщение) , мы инициализируем сообщение об ошибке исключения, и базовый класс позаботится о настройке пользовательского сообщения в соответствии с сообщением .

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

Если входное электронное письмо уже существует в нашей базе данных (в данном случае список), мы создаем наше пользовательское исключение:

public class RegistrationService {  
    List registeredEmails = Arrays.asList("[email protected]", "[email protected]");

    public void validateEmail(String email) throws EmailNotUniqueException {
        if (registeredEmails.contains(email)) {
            throw new EmailNotUniqueException("Email Already Registered");
        }
    }
}

Теперь давайте напишем клиента для нашего сервиса. Поскольку это проверенное исключение, мы должны придерживаться правила обработки или объявления. В следующем примере мы решили “разобраться” с этим:

public class RegistrationServiceClient {  
    public static void main(String[] args) {
        RegistrationService service = new RegistrationService();
        try {
            service.validateEmail("[email protected]");
        } catch (EmailNotUniqueException e) {
            // logging and handling the situation
        }
    }
}

Запуск этого фрагмента кода приведет к:

mynotes.custom.checked.exception.EmailNotUniqueException: Email Already Registered  
    at mynotes.custom.checked.exception.RegistrationService.validateEmail(RegistrationService.java:12)
    at mynotes.custom.checked.exception.RegistrationServiceClient.main(RegistrationServiceClient.java:9)

Примечание: Процесс обработки исключений опущен для краткости, но, тем не менее, является важным процессом.

Пользовательское Непроверенное Исключение

Это прекрасно работает, но наш код стал немного запутанным. Кроме того, мы заставляем клиента фиксировать наше исключение в блоке try-catch . В некоторых случаях это может привести к тому, что разработчики будут вынуждены писать шаблонный код.

В этом случае вместо этого может быть полезно создать пользовательское исключение во время выполнения. Чтобы создать пользовательское непроверенное исключение, мы должны расширить класс java.lang.RuntimeException .

Давайте рассмотрим ситуацию, когда мы должны проверить, имеет ли электронное письмо действительное доменное имя или нет:

public class DomainNotValidException extends RuntimeException {

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

Git Essentials

Ознакомьтесь с этим практическим руководством по изучению Git, содержащим лучшие практики и принятые в отрасли стандарты. Прекратите гуглить команды Git и на самом деле изучите это!

Теперь позвольте использовать его в нашем сервисе:

public class RegistrationService {

    public void validateEmail(String email) {
        if (!isDomainValid(email)) {
            throw new DomainNotValidException("Invalid domain");
        }
    }

    private boolean isDomainValid(String email) {
        List validDomains = Arrays.asList("gmail.com", "yahoo.com", "outlook.com");
        if (validDomains.contains(email.substring(email.indexOf("@") + 1))) {
            return true;
        }
        return false;
    }
}

Обратите внимание, что нам не нужно было использовать ключевые слова throws в подписи метода, поскольку это непроверенное исключение.

Теперь давайте напишем клиента для нашего сервиса. На этот раз нам не нужно использовать try-catch блок:

public class RegistrationServiceClient {

    public static void main(String[] args) {
        RegistrationService service = new RegistrationService();
        service.validateEmail("[email protected]");
    }
}

Запуск этого фрагмента кода приведет к:

Exception in thread "main" mynotes.custom.unchecked.exception.DomainNotValidException: Invalid domain  
    at mynotes.custom.unchecked.exception.RegistrationService.validateEmail(RegistrationService.java:10)
    at mynotes.custom.unchecked.exception.RegistrationServiceClient.main(RegistrationServiceClient.java:7)

Примечание: Конечно, вы можете окружить свой код блоком try-catch , чтобы зафиксировать возникающее исключение, но теперь компилятор не заставляет его выполнять.

Создание исключения, Заключенного в пользовательское исключение

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

Предположим, что ваше приложение имеет стандартный Коды ошибок класс:

public enum ErrorCodes {  
    VALIDATION_PARSE_ERROR(422);

    private int code;

    ErrorCodes(int code) {
        this.code = code;
    }

    public int getCode() {
        return code;
    }
}

Давайте создадим наше пользовательское исключение:

public class InvalidCurrencyDataException extends RuntimeException {

    private Integer errorCode;

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

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

    public InvalidCurrencyDataException(String message, Throwable cause, ErrorCodes errorCode) {
        super(message, cause);
        this.errorCode = errorCode.getCode();
    }

    public Integer getErrorCode() {
        return errorCode;
    }
}

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

Мы также фиксируем код ошибки в одном из конструкторов и устанавливаем код ошибки в самом исключении. Код ошибки может быть использован клиентом для ведения журнала или в любых других целях. Это помогает в более централизованном стандарте обработки исключений.

Давайте напишем наш класс обслуживания:

public class CurrencyService {  
    public String convertDollarsToEuros(String value) {
        try {
        int x = Integer.parseInt(value);
    } catch (NumberFormatException e) {
        throw new InvalidCurrencyDataException("Invalid data", e, ErrorCodes.VALIDATION_PARSE_ERROR);
        }
        return value;
    }
}

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

Давайте напишем тестовый клиент для этой службы:

public class CurrencyClient {  
    public static void main(String[] args) {
        CurrencyService service = new CurrencyService();
    service.convertDollarsToEuros("asd");
    }
}

Выход:

Exception in thread "main" mynotes.custom.unchecked.exception.InvalidCurrencyDataException: Invalid data  
    at mynotes.custom.unchecked.exception.CurrencyService.convertDollarsToEuro(CurrencyService.java:10)
    at mynotes.custom.unchecked.exception.CurrencyClient.main(CurrencyClient.java:8)
Caused by: java.lang.NumberFormatException: For input string: "asd"  
    at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
    at java.lang.Integer.parseInt(Integer.java:580)
    at java.lang.Integer.parseInt(Integer.java:615)
    at mynotes.custom.unchecked.exception.CurrencyService.convertDollarsToEuro(CurrencyService.java:8)
    ... 1 more

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

Рекомендации по использованию пользовательских исключений

  • Придерживайтесь общего соглашения об именовании во всей экосистеме Java – Все имена пользовательских классов исключений должны заканчиваться на “Исключение”
  • Избегайте создания пользовательских исключений, если стандартные исключения из самого JDK могут служить этой цели. В большинстве случаев нет необходимости определять пользовательские исключения.
  • Предпочитайте исключения во время выполнения проверенным исключениям. Фреймворки, такие как Spring , заключили все проверенные исключения в исключения времени выполнения, поэтому не заставляют клиента писать стандартный код, который им не нужен или не нужен.
  • Предоставьте множество перегруженных конструкторов, основанных на том, как будет создаваться пользовательское исключение. Если он используется для повторного создания существующего исключения, то обязательно предоставьте конструктор, который устанавливает причину.

Вывод

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

Код для примеров, используемых в этой статье, можно найти на Github .