Создание собственного класса исключения в Java - Вопросы

Всего: 5 вопросов

1. 

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

Собственное (пользовательское) исключение — это класс, унаследованный от Exception (проверяемое) или от RuntimeException (непроверяемое). Чтобы его создать:

  1. Объявите класс с суффиксом Exception в имени, например InvalidAgeException.
  2. Выберите родителя: Exception или RuntimeException.
  3. Добавьте конструкторы, которые передают сообщение (и при необходимости причину) в super(...).
public class InvalidAgeException extends Exception {
    public InvalidAgeException(String message) {
        super(message);
    }
}

Реализовывать методы не нужно: getMessage(), getCause(), printStackTrace() и toString() класс наследует от Throwable (сам Exception объявляет только конструкторы). Выбрасывают такое исключение оператором throw new InvalidAgeException("Недопустимый возраст: " + age); проверяемое исключение метод должен объявить в throws.

2. 

От какого класса наследовать собственное исключение: Exception или RuntimeException?

Родитель определяет, будет ли исключение проверяемым:

  • extends Exception — проверяемое (checked). Компилятор требует throws или try-catch, иначе ошибка unreported exception ... must be caught or declared to be thrown. Выбирайте, когда ситуация ожидаема и вызывающий код может на неё осмысленно отреагировать: платёж отклонён, файл не найден, неверный пароль.
  • extends RuntimeException — непроверяемое (unchecked). throws и try-catch не обязательны. Выбирайте для ошибок программиста и нарушения контракта метода: некорректный аргумент, недопустимое состояние объекта.

Практическое правило: если поймавший исключение код может что-то сделать (повторить запрос, показать сообщение, выбрать другой путь) — делайте его проверяемым; если единственный выход — исправить код, берите RuntimeException. Spring и Hibernate почти все свои исключения делают непроверяемыми.

Наследовать Error и Throwable не стоит: Error зарезервирован для сбоев JVM (OutOfMemoryError, StackOverflowError), а наследника Throwable не поймает привычный catch (Exception e).

3. 

Какие конструкторы нужны собственному исключению и зачем передавать в него причину (cause)?

Конструкторы не наследуются, поэтому каждый нужный вариант объявляют в своём классе и передают параметры в super(...). Обычно хватает двух:

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

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

Конструктор с cause нужен для цепочки исключений (exception chaining): низкоуровневое исключение оборачивается в своё, а исходная причина остаётся доступна через getCause() и видна в stack trace как Caused by: ...:

catch (NumberFormatException e) {
    throw new OrderProcessingException("Некорректное количество товара: " + input, e);
}

Если не передать e, getCause() вернёт null, а блок Caused by пропадёт из лога.

Всего у Exception (и RuntimeException) пять конструкторов: (), (String message), (String message, Throwable cause), (Throwable cause) и защищённый (String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) из Java 7 — например, super(message, null, false, false) создаёт исключение без stack trace.

4. 

Когда стоит создавать собственное исключение, а когда лучше взять стандартное из JDK?

Своё исключение оправдано, когда:

  • ошибка имеет смысл в предметной области: OrderNotFoundException, WrongPasswordException;
  • нужно передать дополнительные данные (код ошибки, некорректное значение) в полях с геттерами;
  • вызывающий код должен перехватывать именно эту ошибку отдельно от других;
  • на границе слоёв приложения: слой доступа к данным ловит SQLException и выбрасывает своё DataAccessException с причиной, чтобы бизнес-логика не зависела от JDBC.

Для типовых нарушений контракта лучше стандартные исключения — их смысл понятен любому Java-разработчику:

  • недопустимый аргумент — IllegalArgumentException;
  • аргумент null — NullPointerException через Objects.requireNonNull(arg, "...");
  • неподходящее состояние объекта — IllegalStateException;
  • индекс вне диапазона — IndexOutOfBoundsException через Objects.checkIndex(index, length);
  • операция не поддерживается — UnsupportedOperationException.

Если собственных исключений много, их объединяют общим родителем (например, ShopException и его подклассы), чтобы перехватить всю группу одним catch.

5. 

Какие ошибки чаще всего допускают при создании собственного класса исключения?

Большинство проблем связано не с синтаксисом, а с потерей информации:

  • Своё поле message вместо super(message) — getMessage() и stack trace покажут null.
  • Потеря причины — throw new MyException("Ошибка БД") внутри catch (SQLException e) теряет исходное исключение; нужно throw new MyException("Ошибка БД", e).
  • Наследование от Throwable или Error — первое не поймает catch (Exception e), второе вводит в заблуждение: Error означает сбой JVM.
  • Имя без суффикса Exception — формально допустимо, но нарушает соглашение.
  • Предупреждение о serialVersionUID — Throwable реализует Serializable; это не ошибка, убирается полем private static final long serialVersionUID = 1L;.

Также исключение нельзя сделать generic-классом: class MyException<T> extends Exception даёт ошибку компиляции a generic class may not extend java.lang.Throwable (из-за стирания типов catch не различил бы параметры). Record тоже не подходит — он неявно наследует java.lang.Record. А вот статический вложенный класс исключения (UserService.NotFoundException) — обычная практика.

Страница 1 из 1