1. Обзор
Проверка существования класса может быть полезна при определении используемой реализации интерфейса. Этот метод обычно используется в старых установках JDBC.
В этом руководстве мы рассмотрим нюансы использования Class.forName()
для проверки существования класса в пути к классам Java .
2. Использование Class.forName()
Мы можем проверить существование класса с помощью Java Reflection , в частности, Class.forName()
. Документация показывает, что если класс не может быть найден, будет выброшено исключение ClassNotFoundException .
2.1. Когда ожидать ClassNotFoundException
Во-первых, давайте напишем тест, который обязательно вызовет ClassNotFoundException
, чтобы мы могли знать, что наши положительные тесты безопасны:
@Test(expected = ClassNotFoundException.class)
public void givenNonExistingClass_whenUsingForName_thenClassNotFound() throws ClassNotFoundException {
Class.forName("class.that.does.not.exist");
}
Итак, мы доказали, что несуществующий класс вызовет исключение ClassNotFoundException
. Давайте напишем тест для действительно существующего класса:
@Test
public void givenExistingClass_whenUsingForName_thenNoException() throws ClassNotFoundException {
Class.forName("java.lang.String");
}
Эти тесты доказывают, что запуск Class.forName()
без перехвата ClassNotFoundException
эквивалентен указанному классу, существующему в пути к классам . Однако это не совсем идеальное решение из-за побочных эффектов .
2.2. Побочный эффект: инициализация класса
Важно отметить, что без указания загрузчика класса Class.forName()
должен запустить статический инициализатор в запрошенном классе . Это может привести к неожиданному поведению.
Чтобы проиллюстрировать это поведение, давайте создадим класс, который генерирует исключение RuntimeException
при выполнении его статического блока инициализатора, чтобы мы могли сразу узнать, когда он выполняется:
public static class InitializingClass {
static {
if (true) { //enable throwing of an exception in a static initialization block
throw new RuntimeException();
}
}
}
Из документации forName()
мы можем видеть , что он выдает ошибку ExceptionInInitializerError
, если инициализация, вызванная этим методом, не удалась.
Давайте напишем тест, который будет ожидать ExceptionInInitializerError
при попытке найти наш InitializingClass
без указания загрузчика классов:
@Test(expected = ExceptionInInitializerError.class)
public void givenInitializingClass_whenUsingForName_thenInitializationError() throws ClassNotFoundException {
Class.forName("path.to.InitializingClass");
}
Поскольку выполнение статического блока инициализации класса является невидимым побочным эффектом, теперь мы можем видеть, как это может вызвать проблемы с производительностью или даже ошибки. Давайте посмотрим, как пропустить инициализацию класса.
3. Указание Class.forName()
пропустить инициализацию
К счастью для нас, существует перегруженный метод forName(),
который принимает загрузчик класса и указывает, следует ли выполнять инициализацию класса.
Согласно документации следующие вызовы эквивалентны:
Class.forName("Foo")
Class.forName("Foo", true, this.getClass().getClassLoader())
Изменив true
на false
, теперь мы можем написать тест, который проверяет существование нашего класса InitializingClass ,
не вызывая его статический блок инициализации :
@Test
public void givenInitializingClass_whenUsingForNameWithoutInitialization_thenNoException() throws ClassNotFoundException {
Class.forName("path.to.InitializingClass", false, getClass().getClassLoader());
}
4. Модули Java 9
Для проектов Java 9+ существует третья перегрузка Class.forName()
, которая принимает имя класса Module
и String .
Эта перегрузка не запускает инициализатор класса по умолчанию. Кроме того, в частности, он возвращает null
, когда запрошенный класс не существует, а не создает исключение ClassNotFoundException
.
5. Вывод
В этом кратком руководстве мы рассмотрели побочный эффект инициализации класса при использовании Class.forName()
и обнаружили, что вы можете использовать перегрузки forName()
, чтобы предотвратить это.
Исходный код со всеми примерами из этого руководства можно найти на GitHub .