Wann löst Java den ExceptionInInitializerError aus?

1. Übersicht

In diesem kurzen Tutorial werden wir sehen, warum Java eine Instanz der ExceptionInInitializerError- Ausnahme auslöst .

Wir werden mit ein bisschen Theorie beginnen. Dann sehen wir einige Beispiele für diese Ausnahme in der Praxis.

2. Der ExceptionInInitializerError

Der ExceptionInInitializerError zeigt an, dass in einem statischen Initialisierer eine unerwartete Ausnahme aufgetreten ist. Wenn wir diese Ausnahme sehen, sollten wir grundsätzlich wissen, dass Java keinen statischen Initialisierungsblock auswerten oder eine statische Variable instanziieren konnte.

Tatsächlich schließt Java jedes Mal, wenn eine Ausnahme in einem statischen Initialisierer auftritt, diese Ausnahme automatisch in eine Instanz der ExceptionInInitializerError- Klasse ein. Auf diese Weise wird auch ein Verweis auf die tatsächliche Ausnahme als Grundursache beibehalten.

Nachdem wir nun die Gründe für diese Ausnahme kennen, wollen wir sie in der Praxis sehen.

3. Statischer Initialisierungsblock

Um einen fehlgeschlagenen statischen Blockinitialisierer zu haben, teilen wir absichtlich eine Ganzzahl durch Null:

public class StaticBlock { private static int state; static { state = 42 / 0; } }

Wenn wir nun die Klasseninitialisierung mit etwas auslösen wie:

new StaticBlock();

Dann würden wir die folgende Ausnahme sehen:

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

Wie bereits erwähnt, löst Java die ExceptionInInitializerError- Ausnahme aus, während ein Verweis auf die Grundursache beibehalten wird:

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

Erwähnenswert ist auch, dass die Methode ist eine Klasseninitialisierungsmethode in der JVM.

4. Initialisierung der statischen Variablen

Dasselbe passiert, wenn Java eine statische Variable nicht initialisiert:

public class StaticVar { private static int state = initializeState(); private static int initializeState() { throw new RuntimeException(); } }

Nochmals, wenn wir den Klasseninitialisierungsprozess auslösen:

new StaticVar();

Dann tritt die gleiche Ausnahme auf:

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.(ExceptionInInitializerErrorUnitTest.java:23) ... 23 more

Ähnlich wie bei statischen Initialisierungsblöcken bleibt auch die Grundursache der Ausnahme erhalten:

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

5. Überprüfte Ausnahmen

Als Teil der Java-Sprachspezifikation (JLS-11.2.3) können wir keine geprüften Ausnahmen in einen statischen Initialisiererblock oder einen statischen Variableninitialisierer werfen. Zum Beispiel, wenn wir dies versuchen:

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

Der Compiler würde mit dem folgenden Kompilierungsfehler fehlschlagen:

java: initializer must be able to complete normally

Als Konvention sollten wir die möglichen geprüften Ausnahmen in eine Instanz von ExceptionInInitializerError einschließen, wenn unsere statische Initialisierungslogik eine geprüfte Ausnahme auslöst:

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

Wie oben gezeigt, löst die Methode getDeclaredConstructor () eine aktivierte Ausnahme aus. Aus diesem Grund haben wir die aktivierte Ausnahme abgefangen und gemäß der Konvention verpackt.

Da wir bereits explizit eine Instanz der ExceptionInInitializerError- Ausnahme zurückgeben, wird Java diese Ausnahme nicht in eine weitere ExceptionInInitializerError- Instanz einschließen.

Wenn wir jedoch eine andere ungeprüfte Ausnahme auslösen, löst Java einen weiteren ExceptionInInitializerError aus :

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

Hier verpacken wir die aktivierte Ausnahme in eine nicht aktivierte. Da diese nicht aktivierte Ausnahme keine Instanz von ExceptionInInitializerError ist, wird sie von Java erneut umbrochen, was zu dieser unerwarteten Stapelverfolgung führt:

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

Wie oben gezeigt, wäre die Stapelverfolgung viel sauberer, wenn wir der Konvention folgen.

5.1. OpenJDK

In letzter Zeit wird diese Konvention sogar im OpenJDK-Quellcode selbst verwendet. So verwendet die AtomicReference beispielsweise diesen Ansatz:

public class AtomicReference 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. Fazit

In diesem Tutorial haben wir gesehen, warum Java eine Instanz der ExceptionInInitializerError- Ausnahme auslöst .

Wie üblich sind alle Beispiele auf GitHub verfügbar.