1. 概要

このクイックチュートリアルでは、JavaがExceptionInInitializerError例外のインスタンスをスローする原因を確認します。

少し理論から始めましょう。 次に、この例外の実際の例をいくつか見ていきます。

2. ExceptionInInitializerError

ExceptionInInitializerErrorは、静的初期化子で予期しない例外が発生したことを示します。 基本的に、この例外が発生した場合、Javaが静的初期化ブロックの評価または静的変数のインスタンス化に失敗したことを知っておく必要があります。

実際、静的初期化子内で例外が発生するたびに、Javaはその例外をExceptionInInitializerErrorクラスのインスタンス内に自動的にラップします。 このようにして、根本的な原因として実際の例外への参照も維持します。

この例外の背後にある理論的根拠がわかったので、実際に見てみましょう。

3. 静的イニシャライザーブロック

静的ブロック初期化子が失敗するようにするには、整数を意図的にゼロで除算します。

public class StaticBlock {

    private static int state;

    static {
        state = 42 / 0;
    }
}

ここで、次のようなものでクラスの初期化をトリガーするとします。

new StaticBlock();

次に、次の例外が表示されます。

java.lang.ExceptionInInitializerError
    at com.baeldung...(ExceptionInInitializerErrorUnitTest.java:18)
Caused by: java.lang.ArithmeticException: / by zero
    at com.baeldung.StaticBlock.<clinit>(ExceptionInInitializerErrorUnitTest.java:35)
    ... 23 more

前述のように、Javaは、根本原因への参照を維持しながら、ExceptionInInitializerError例外をスローします。

assertThatThrownBy(StaticBlock::new)
  .isInstanceOf(ExceptionInInitializerError.class)
  .hasCauseInstanceOf(ArithmeticException.class);

また、言及する価値がありますメソッドはクラス初期化メソッド JVMで。

4. 静的変数の初期化

Javaが静的変数の初期化に失敗した場合も、同じことが起こります。

public class StaticVar {

    private static int state = initializeState();

    private static int initializeState() {
        throw new RuntimeException();
    }
}

ここでも、クラス初期化プロセスをトリガーすると、次のようになります。

new StaticVar();

次に、同じ例外が発生します。

java.lang.ExceptionInInitializerError
    at com.baeldung...(ExceptionInInitializerErrorUnitTest.java:11)
Caused by: java.lang.RuntimeException
    at com.baeldung.StaticVar.initializeState(ExceptionInInitializerErrorUnitTest.java:26)
    at com.baeldung.StaticVar.<clinit>(ExceptionInInitializerErrorUnitTest.java:23)
    ... 23 more

静的初期化ブロックと同様に、例外の根本原因も保持されます。

assertThatThrownBy(StaticVar::new)
  .isInstanceOf(ExceptionInInitializerError.class)
  .hasCauseInstanceOf(RuntimeException.class);

5. チェックされた例外

Java言語仕様(JLS-11.2.3)の一部として、静的初期化ブロックまたは静的変数初期化子内にチェック済み例外をスローすることはできません。 たとえば、そうしようとすると、次のようになります。

public class NoChecked {
    static {
        throw new Exception();
    }
}

コンパイラは次のコンパイルエラーで失敗します。

java: initializer must be able to complete normally

慣例として、静的初期化ロジックがチェック済み例外をスローする場合は、ExceptionInInitializerErrorのインスタンス内で可能なチェック済み例外をラップする必要があります。

public class CheckedConvention {

    private static Constructor<?> constructor;

    static {
        try {
            constructor = CheckedConvention.class.getDeclaredConstructor();
        } catch (NoSuchMethodException e) {
            throw new ExceptionInInitializerError(e);
        }
    }
}

上に示したように、 getDeclaredConstructor()メソッドはチェックされた例外をスローします。 したがって、チェックされた例外をキャッチし、規則が示すようにラップしました。

ExceptionInInitializerError 例外のインスタンスを明示的に返しているため、Javaはこの例外をさらに別のExceptionInInitializerErrorインスタンス内にラップしません。

ただし、他のチェックされていない例外をスローすると、Javaは別のExceptionInInitializerErrorをスローします。

static {
    try {
        constructor = CheckedConvention.class.getConstructor();
    } catch (NoSuchMethodException e) {
        throw new RuntimeException(e);
    }
}

ここでは、チェックされた例外をチェックされていない例外の中にラップしています。 このチェックされていない例外はExceptionInInitializerErrorのインスタンスではないため、 Javaはそれを再度ラップし、予期しないスタックトレースが発生します。

java.lang.ExceptionInInitializerError
	at com.baeldung.exceptionininitializererror...
Caused by: java.lang.RuntimeException: java.lang.NoSuchMethodException: ...
Caused by: java.lang.NoSuchMethodException: com.baeldung.CheckedConvention.<init>()
	at java.base/java.lang.Class.getConstructor0(Class.java:3427)
	at java.base/java.lang.Class.getConstructor(Class.java:2165)

上に示したように、規則に従うと、スタックトレースはこれよりもはるかにクリーンになります。

5.1. OpenJDK

最近、この規則はOpenJDKソースコード自体でも使用されています。 たとえば、AtomicReferenceがこのアプローチをどのように使用しているかを次に示します。

public class AtomicReference<V> implements java.io.Serializable {
    private static final VarHandle VALUE;
    static {
        try {
            MethodHandles.Lookup l = MethodHandles.lookup();
            VALUE = l.findVarHandle(AtomicReference.class, "value", Object.class);
        } catch (ReflectiveOperationException e) {
            throw new ExceptionInInitializerError(e);
        }
    }

    private volatile V value;

   // omitted
}

6. 結論

このチュートリアルでは、JavaがExceptionInInitializerError例外のインスタンスをスローする原因を確認しました。

いつものように、すべての例はGitHubから入手できます。