Рубрики
Без рубрики

Когда Java выбрасывает ошибку ExceptionInInitializerError?

Узнайте, что заставляет Java выдавать ExceptionInInitializerError, используя несколько практических примеров

Автор оригинала: Ali Dehghani.

1. Обзор

В этом кратком руководстве мы увидим, что заставляет Java выбрасывать экземпляр ExceptionInInitializerError исключение.

Начнем с небольшой теории. Затем мы увидим несколько примеров этого исключения на практике.

2. Ошибка exceptioninitializererror

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

В качестве соглашения мы должны обернуть возможные проверенные исключения внутри экземпляра Исключение ininitializererror когда наша статическая логика инициализации выдает проверенное исключение:

public class CheckedConvention {

    private static Constructor constructor;

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

Как показано выше, метод getDeclaredConstructor() вызывает проверенное исключение. Поэтому мы поймали проверенное исключение и завернули его, как предполагает конвенция.

Поскольку мы уже возвращаем экземпляр Исключение ininitializererror исключение явно, Java не будет заключать это исключение в еще одно Исключение ininitializererror пример.

Однако, если мы создадим любое другое непроверенное исключение, 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 exception.

Как обычно, все примеры доступны на GitHub .