1. Обзор
Исключения обеспечивают отделение кода обработки ошибок от обычного потока приложения. Нередко возникает исключение во время создания экземпляра объекта.
В этой статье мы рассмотрим все детали создания исключений в конструкторах.
2. Генерация исключений в конструкторах
Конструкторы — это специальные типы методов, вызываемые для создания объекта. В следующих разделах мы рассмотрим, как генерировать исключения, какие исключения генерировать и почему мы должны генерировать исключения в конструкторах.
2.1. Как?
Генерация исключений в конструкторе ничем не отличается от этого в любом другом методе. Начнем с создания класса Animal с конструктором без аргументов:
public Animal() throws InstantiationException {
throw new InstantiationException("Cannot be instantiated");
}
Здесь мы выбрасываем InstantiationException
, которое является проверенным исключением .
2.2. Который?
Несмотря на то, что допускается генерация любого типа исключений, давайте установим некоторые рекомендации.
Во-первых, мы не хотим бросать « java.lang.Exception»
. Это связано с тем, что вызывающая сторона не может определить тип исключения и тем самым обработать его.
Во-вторых, мы должны генерировать проверенное исключение, если вызывающая сторона вынуждена его обрабатывать.
В-третьих, мы должны генерировать непроверенное исключение, если вызывающий объект не может восстановиться после исключения.
Важно отметить, что эти практики одинаково применимы как для методов, так и для конструкторов .
2.3. Почему?
В этом разделе давайте разберемся, почему мы можем захотеть генерировать исключения в конструкторе.
Проверка аргумента является распространенным вариантом использования исключений в конструкторе. Конструкторы в основном используются для присвоения значений переменных. Если аргументы, переданные конструктору, недействительны, мы можем генерировать исключения. Рассмотрим быстрый пример:
public Animal(String id, int age) {
if (id == null)
throw new NullPointerException("Id cannot be null");
if (age < 0)
throw new IllegalArgumentException("Age cannot be negative");
}
В приведенном выше примере мы выполняем проверку аргумента перед инициализацией объекта. Это помогает гарантировать, что мы создаем только допустимые объекты.
Здесь, если идентификатор
, переданный объекту Animal , равен
null
, мы можем сгенерировать исключение NullPointerException.
Для аргументов, которые не являются нулевыми, но все же недействительными, например отрицательное значение для возраста
, мы можем сгенерировать исключение IllegalArgumentException
.
Проверки безопасности — еще один распространенный случай создания исключений в конструкторе. Некоторые объекты требуют проверки безопасности при их создании. Мы можем генерировать исключения, если конструктор выполняет потенциально небезопасную или конфиденциальную операцию.
Давайте рассмотрим, что наш класс Animal
загружает атрибуты из файла пользовательского ввода:
public Animal(File file) throws SecurityException, IOException {
if (file.isAbsolute()) {
throw new SecurityException("Traversal attempt");
}
if (!file.getCanonicalPath()
.equals(file.getAbsolutePath())) {
throw new SecurityException("Traversal attempt");
}
}
В нашем примере выше мы предотвратили атаку Path Traversal . Это достигается за счет запрета абсолютных путей и обхода каталогов. Например, рассмотрим файл «a/../b.txt». Здесь канонический путь и абсолютный путь отличаются, что может быть потенциальной атакой с обходом каталога.
3. Унаследованные исключения в конструкторах
Теперь поговорим об обработке исключений суперкласса в конструкторах.
Давайте создадим дочерний класс Bird
, который расширяет наш класс Animal
:
public class Bird extends Animal {
public Bird() throws ReflectiveOperationException {
super();
}
public Bird(String id, int age) {
super(id, age);
}
}
Поскольку super()
должна быть первой строкой в конструкторе, мы не можем просто вставить блок try-catch
для обработки проверенного исключения, созданного суперклассом.
Поскольку наш родительский класс Animal
генерирует проверенное исключение InstantiationException
, мы не можем обработать исключение в конструкторе Bird
. Вместо этого мы можем распространить то же исключение или его родительское исключение.
Важно отметить, что правила обработки исключений в отношении переопределения методов отличаются. При переопределении метода, если метод суперкласса объявляет исключение, переопределенный метод подкласса может объявить то же самое, исключение подкласса или исключение, но не может объявить родительское исключение.
С другой стороны, непроверенные исключения не нужно объявлять, и они не могут обрабатываться внутри конструкторов подклассов.
4. Вопросы безопасности
Генерация исключения в конструкторе может привести к частично инициализированным объектам. Как описано в Руководстве 7.3 Руководства по безопасному кодированию Java , частично инициализированные объекты нефинального класса подвержены угрозе безопасности, известной как атака финализатора.
Короче говоря, атака Finalizer вызывается подклассом частично инициализированных объектов и переопределением его метода finalize()
, а также попытками создать новый экземпляр этого подкласса. Возможно, это позволит обойти проверки безопасности, выполняемые внутри конструктора подкласса.
Переопределение метода finalize()
и пометка его как final
может предотвратить эту атаку.
Однако метод finalize()
устарел в Java 9, что предотвращает атаки такого типа.
5. Вывод
В этом руководстве мы узнали о создании исключений в конструкторах, а также о связанных с этим преимуществах и проблемах безопасности. Кроме того, мы рассмотрели некоторые рекомендации по генерации исключений в конструкторах.
Как всегда, исходный код, используемый в этом руководстве, доступен на GitHub .