Кога Java изхвърля грешката ExceptionInInitializerError?

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