Создание собственного класса исключения в Java - Вопросы
Всего: 5 вопросов
1. Как создать собственное исключение в Java?
Как создать собственное исключение в Java?
Собственное (пользовательское) исключение — это класс, унаследованный от Exception (проверяемое) или от RuntimeException (непроверяемое). Чтобы его создать:
- Объявите класс с суффиксом
Exceptionв имени, напримерInvalidAgeException. - Выберите родителя:
ExceptionилиRuntimeException. - Добавьте конструкторы, которые передают сообщение (и при необходимости причину) в
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?
От какого класса наследовать собственное исключение: 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)?
Какие конструкторы нужны собственному исключению и зачем передавать в него причину (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?
Когда стоит создавать собственное исключение, а когда лучше взять стандартное из 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) — обычная практика.