Checking if a Class Exists in Java – 检查一个类是否存在于Java中

最后修改: 2020年 9月 13日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

Checking for the existence of a class could be useful when determining which implementation of an interface to use. This technique is commonly used during older JDBC setups.

在确定使用哪个接口的实现时,检查一个类的存在是很有用的。这种技术通常在旧的JDBC设置中使用。

In this tutorial, we’ll explore the nuances of using Class.forName() to check the existence of a class in the Java classpath.

在本教程中,我们将探索使用Class.forName()来检查Java classpath中是否存在一个类的细微差别

2. Using Class.forName()

2.使用Class.forName()

We can check for the existence of a class using Java Reflection, specifically Class.forName(). The documentation shows that a ClassNotFoundException will be thrown if the class cannot be located.

我们可以使用Java Reflection,特别是Class.forName()来检查一个类的存在。文档显示,如果无法找到该类,将抛出一个ClassNotFoundException

2.1. When to Expect ClassNotFoundException

2.1.何时期待ClassNotFoundException

First, let’s write a test that will certainly throw a ClassNotFoundException so that we can know that our positive tests are safe:

首先,让我们写一个测试,它肯定会抛出一个ClassNotFoundException,这样我们就能知道我们的正面测试是安全的。

@Test(expected = ClassNotFoundException.class)
public void givenNonExistingClass_whenUsingForName_thenClassNotFound() throws ClassNotFoundException {
    Class.forName("class.that.does.not.exist");
}

So, we’ve proven that a class that doesn’t exist will throw a ClassNotFoundException. Let’s write a test for a class that indeed does exist:

所以,我们已经证明,一个不存在的类会抛出一个ClassNotFoundException。让我们为一个确实存在的类写一个测试。

@Test
public void givenExistingClass_whenUsingForName_thenNoException() throws ClassNotFoundException {
    Class.forName("java.lang.String");
}

These tests prove that running Class.forName() and not catching a ClassNotFoundException is equivalent to the specified class existing on the classpath. However, this isn’t quite a perfect solution due to side effects.

这些测试证明,运行Class.forName()并且没有捕捉到ClassNotFoundException就等同于指定的类存在于classpath上。然而,由于side effects,这并不是一个很完美的解决方案。

2.2. Side Effect: Class Initialization

2.2.副作用 类的初始化

It is essential to point out that, without specifying a class loader, Class.forName() has to run the static initializer on the requested class. This can lead to unexpected behavior.

必须指出的是,如果不指定类加载器,Class.forName()就必须在请求的类上运行静态初始化器。这可能会导致意外的行为。

To exemplify this behavior, let’s create a class that throws a RuntimeException when its static initializer block is executed so that we can know immediately when it is executed:

为了说明这种行为,让我们创建一个类,当它的静态初始化块被执行时抛出一个RuntimeException,这样我们就可以立即知道它被执行的时间。

public static class InitializingClass {
    static {
        if (true) { //enable throwing of an exception in a static initialization block
            throw new RuntimeException();
        }
    }
}

We can see from the forName() documentation that it throws an ExceptionInInitializerError if the initialization provoked by this method fails.

我们可以从forName()文档中看到,如果这个方法引发的初始化失败,它会抛出一个ExceptionInitializerError

Let’s write a test that will expect an ExceptionInInitializerError when trying to find our InitializingClass without specifying a class loader:

让我们写一个测试,当试图找到我们的InitializingClass而没有指定一个类加载器时,它将期待一个ExceptionInitializerError

@Test(expected = ExceptionInInitializerError.class)
public void givenInitializingClass_whenUsingForName_thenInitializationError() throws ClassNotFoundException {
    Class.forName("path.to.InitializingClass");
}

Since the execution of a class’ static initialization block is an invisible side effect, we can now see how it could cause performance issues or even errors. Let’s look at how to skip the class initialization.

由于类的静态初始化块的执行是一个不可见的副作用,我们现在可以看到它是如何导致性能问题甚至是错误。让我们来看看如何跳过类的初始化。

3. Telling Class.forName() to Skip Initialization

3.告诉Class.forName()跳过初始化

Luckily for us, there is an overloaded method of forName(), which accepts a class loader and whether the class initialization should be executed.

幸运的是,我们有一个forName()的重载方法,它接受一个类加载器和是否应该执行类初始化。

According to the documentation, the following calls are equivalent:

根据文档,以下调用是等效的。

Class.forName("Foo")
Class.forName("Foo", true, this.getClass().getClassLoader())

By changing true to false, we can now write a test that checks for the existence of our InitializingClass without triggering its static initialization block:

通过将true改为false,我们现在可以写一个测试来检查我们的InitializingClass的存在而不触发其静态初始化块

@Test
public void givenInitializingClass_whenUsingForNameWithoutInitialization_thenNoException() throws ClassNotFoundException {
    Class.forName("path.to.InitializingClass", false, getClass().getClassLoader());
}

4. Java 9 Modules

4.Java 9模块

For Java 9+ projects, there’s a third overload of Class.forName(), which accepts a Module and a String class name. This overload doesn’t run the class initializer by default. Also, notably, it returns null when the requested class does not exist rather than throwing a ClassNotFoundException.

对于Java 9以上的项目,有一个Class.forName()的第三个重载,它接受一个Module和一个String类名。这个重载默认不会运行类的初始化器。另外,值得注意的是,当请求的类不存在时,它会返回null,而不是抛出ClassNotFoundException

5. Conclusion

5.总结

In this short tutorial, we’ve exposed the side effect of class initialization when using Class.forName() and have found that you can use the forName() overloads to prevent that from happening.

在这个简短的教程中,我们已经暴露了使用Class.forName()时类初始化的副作用,并且发现你可以使用forName()重载来防止这种情况发生。

The source code with all the examples in this tutorial can be found over on GitHub.

本教程中包含所有示例的源代码可以在GitHub上找到over