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がスローされることがわかります。

クラスローダーを指定せずにInitializingClassを見つけようとすると、ExceptionInInitializerErrorを期待するテストを書いてみましょう。

@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())

truefalseに変更することで、静的初期化ブロックをトリガーせずに、 InitializingClassの存在をチェックするテストを作成できるようになりました。 :

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

4. Java9モジュール

Java 9以降のプロジェクトの場合、 Class.forName()の3番目のオーバーロードがあり、ModuleStringクラス名を受け入れます。 このオーバーロードは、デフォルトではクラス初期化子を実行しません。 また、特に、 ClassNotFoundException をスローするのではなく、要求されたクラスが存在しない場合にnullを返します。

5. 結論

この短いチュートリアルでは、 Class.forName()を使用する場合のクラス初期化の副作用を明らかにし、 forName()オーバーロードを使用してそれを防ぐことができることを発見しました。ハプニング。

このチュートリアルのすべての例を含むソースコードは、GitHubにあります。