Перейти к основному содержимому

Проверка существования класса в Java

· 3 мин. чтения

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 .