JavaはいつExceptionInInitializerErrorをスローしますか?
1. 概要
このクイックチュートリアルでは、JavaがExceptionInInitializerError例外のインスタンスをスローする原因を確認します。
少し理論から始めましょう。 次に、この例外の実際の例をいくつか見ていきます。
2. ExceptionInInitializerError
実際、静的初期化子内で例外が発生するたびに、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);
また、言及する価値があります
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でから入手できます。