1. Overview

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.

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

2. Using 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.

2.1. When to Expect ClassNotFoundException

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

@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:

@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.

2.2. Side Effect: Class Initialization

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.

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:

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.

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

@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

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

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:

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

4. Java 9 Modules

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.

5. Conclusion

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.

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


» 下一篇: Java周报,151