When Does Java Throw the ExceptionInInitializerError? – Java什么时候抛出ExceptionInInitializerError?

最后修改: 2020年 7月 6日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

In this quick tutorial, we’re going to see what causes Java to throw an instance of the ExceptionInInitializerError exception.

在这个快速教程中,我们要看看是什么原因导致Java抛出一个ExceptionInInitializerError异常的实例。

We’ll start with a little bit of theory. Then we’ll see a few examples of this exception in practice.

我们将从一点理论开始。然后我们将看到这种例外情况在实践中的几个例子。

2. The ExceptionInInitializerError

2.ExceptionInInitializerError

The ExceptionInInitializerError indicates that an unexpected exception has occurred in a static initializerBasically, when we see this exception, we should know that Java failed to evaluate a static initializer block or to instantiate a static variable.

ExceptionInitializerError表示在静态初始化器中发生了一个意外的异常。基本上,当我们看到这个异常时,我们应该知道,Java未能评估一个静态初始化块或实例化一个静态变量。

In fact, every time any exception happens inside a static initializer, Java automatically wraps that exception inside an instance of the ExceptionInInitializerError class. This way, it also maintains a reference to the actual exception as the root cause.

事实上,每次在静态初始化器中发生任何异常时,Java都会自动将该异常包裹在ExceptionInInitializerError类的实例中。这样一来,它也保持了对实际异常的引用,作为根本原因。

Now that we know the rationale behind this exception, let’s see it in practice.

现在我们知道了这个例外情况背后的原理,让我们看看它的实际情况。

3. Static Initializer Block

3.静态初始化块

To have a failed static block initializer, we’re going to divide an integer by zero intentionally:

为了有一个失败的静态块初始化器,我们要有意地将一个整数除以0。

public class StaticBlock {

    private static int state;

    static {
        state = 42 / 0;
    }
}

Now if we trigger the class initialization with something like:

现在,如果我们用这样的方式触发类的初始化。

new StaticBlock();

Then, we would see the following exception:

然后,我们会看到以下异常。

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

As mentioned earlier, Java throws the ExceptionInInitializerError exception while maintaining a reference to the root cause:

如前所述,Java抛出ExceptionInInitializerError异常,同时保持对根本原因的引用。

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

It’s also worth mentioning that the <clinit> method is a class initialization method in the JVM.

值得一提的是,<clinit> 方法是JVM中的类初始化方法

4. Static Variable Initialization

4.静态变量初始化

The same thing happens if Java fails to initialize a static variable:

如果Java不能初始化一个静态变量,也会发生同样的事情。

public class StaticVar {

    private static int state = initializeState();

    private static int initializeState() {
        throw new RuntimeException();
    }
}

Again, if we trigger the class initialization process:

同样,如果我们触发类的初始化过程。

new StaticVar();

Then the same exception occurs:

然后发生同样的异常。

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

Similar to static initializer blocks, the root cause of the exception is also preserved:

与静态初始化块类似,异常的根本原因也被保留下来。

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

5. Checked Exceptions

5.被检查的异常情况

As part of the Java language specification (JLS-11.2.3), we can’t throw checked exceptions inside a static initializer block or static variable initializer. For instance, if we try to do so:

作为Java语言规范(JLS-11.2.3)的一部分,我们不能在静态初始化块或静态变量初始化器内抛出checked异常。例如,如果我们试图这样做。

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

The compiler would fail with the following compilation error:

编译器会出现以下编译错误。

java: initializer must be able to complete normally

As a convention, we should wrap the possible checked exceptions inside an instance of ExceptionInInitializerError when our static initialization logic throws a checked exception:

按照惯例,当我们的静态初始化逻辑抛出一个检查过的异常时,我们应该将可能的检查过的异常包裹在ExceptionInInitializerError的实例中:

public class CheckedConvention {

    private static Constructor<?> constructor;

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

As shown above, the getDeclaredConstructor() method throws a checked exception. Therefore, we caught the checked exception and wrapped it as the convention suggests.

如上所示,getDeclaredConstructor()方法抛出了一个检查性异常。因此,我们捕获了这个被检查的异常,并按照惯例将其包装起来。

Since we’re already returning an instance of ExceptionInInitializerError exception explicitly, Java won’t wrap this exception inside yet another ExceptionInInitializerError instance.

由于我们已经明确地返回了一个ExceptionInInitializerError异常的实例,Java不会将这个异常包裹在另一个ExceptionInitializerError实例中。

However, if we throw any other unchecked exception, Java will throw another ExceptionInInitializerError:

然而,如果我们抛出任何其他未检查的异常,Java将抛出另一个ExceptionInInitializerError

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

Here, we’re wrapping the checked exception inside an unchecked one. Because this unchecked exception is not an instance of ExceptionInInitializerError, Java will wrap it again, resulting in this unexpected stack trace:

在这里,我们把已检查的异常包裹在一个未检查的异常里面。因为这个未检查的异常不是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)

As shown above, if we follow the convention, then the stack trace would be much cleaner than this.

如上所示,如果我们遵循惯例,那么堆栈跟踪将比这干净得多。

5.1. OpenJDK

5.1. OpenJDK

Recently, this convention is even used in the OpenJDK source code itself. For instance, here’s how the AtomicReference is using this approach:

最近,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. Conclusion

6.结语

In this tutorial, we saw what causes Java to throw an instance of the ExceptionInInitializerError exception.

在本教程中,我们看到了导致Java抛出一个ExceptionInInitializerError异常实例的原因。

As usual, all the examples are available over on GitHub.

像往常一样,所有的例子都可以在GitHub上找到