Quand Java lève-t-il l'erreur ExceptionInInitializerError?

1. Vue d'ensemble

Dans ce didacticiel rapide, nous allons voir ce qui pousse Java à lancer une instance de l' exception ExceptionInInitializerError.

Nous allons commencer par un peu de théorie. Ensuite, nous verrons quelques exemples de cette exception dans la pratique.

2. ExceptionInInitializerError

Le ExceptionInInitializerError indique qu'une exception inattendue est survenue dans un initialiseur statique. Fondamentalement, lorsque nous voyons cette exception, nous devons savoir que Java n'a pas réussi à évaluer un bloc d'initialisation statique ou à instancier une variable statique.

En fait, chaque fois qu'une exception se produit dans un initialiseur statique, Java encapsule automatiquement cette exception dans une instance de la classe ExceptionInInitializerError . De cette façon, il conserve également une référence à l'exception réelle comme cause première.

Maintenant que nous connaissons la raison d'être de cette exception, voyons-la en pratique.

3. Bloc d'initialisation statique

Pour avoir un initialiseur de bloc statique échoué, nous allons diviser un entier par zéro intentionnellement:

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

Maintenant, si nous déclenchons l'initialisation de la classe avec quelque chose comme:

new StaticBlock();

Ensuite, nous verrions l'exception suivante:

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

Comme mentionné précédemment, Java lève l' exception ExceptionInInitializerError tout en conservant une référence à la cause première:

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

Il convient également de mentionner que le method est une méthode d'initialisation de classe dans la JVM.

4. Initialisation de variable statique

La même chose se produit si Java ne parvient pas à initialiser une variable statique:

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

Encore une fois, si nous déclenchons le processus d'initialisation de classe:

new StaticVar();

Ensuite, la même exception se produit:

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

Semblable aux blocs d'initialisation statiques, la cause première de l'exception est également préservée:

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

5. Exceptions vérifiées

Dans le cadre de la spécification du langage Java (JLS-11.2.3), nous ne pouvons pas lancer d'exceptions vérifiées dans un bloc d'initialisation statique ou un initialiseur de variable statique. Par exemple, si nous essayons de le faire:

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

Le compilateur échouerait avec l'erreur de compilation suivante:

java: initializer must be able to complete normally

Par convention, nous devons encapsuler les éventuelles exceptions vérifiées dans une instance de ExceptionInInitializerError lorsque notre logique d'initialisation statique lève une exception vérifiée:

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

Comme indiqué ci-dessus, la méthode getDeclaredConstructor () lève une exception vérifiée. Par conséquent, nous avons détecté l'exception vérifiée et l'avons encapsulée comme le suggère la convention.

Étant donné que nous renvoyons déjà une instance de l' exception ExceptionInInitializerError de manière explicite, Java n'encapsulera pas cette exception dans une autre instance ExceptionInInitializerError .

Cependant, si nous lançons une autre exception non vérifiée, Java lancera une autre ExceptionInInitializerError :

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

Ici, nous enveloppons l'exception vérifiée dans une exception non vérifiée. Étant donné que cette exception non vérifiée n'est pas une instance de ExceptionInInitializerError, Java l'enveloppera à nouveau, ce qui entraînera cette trace de pile inattendue:

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)

Comme indiqué ci-dessus, si nous suivons la convention, la trace de la pile serait beaucoup plus propre que cela.

5.1. OpenJDK

Récemment, cette convention est même utilisée dans le code source d'OpenJDK lui-même. Par exemple, voici comment AtomicReference utilise cette approche:

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

Dans ce didacticiel, nous avons vu ce qui pousse Java à lancer une instance de l' exception ExceptionInInitializerError.

Comme d'habitude, tous les exemples sont disponibles sur over sur GitHub.