为什么 Java 不允许从静态初始化块中抛出已检查异常?
从技术上讲,您可以做到这一点。但是,检查的异常必须在块内捕获。
实际的 Java 限制是不允许已检查的异常传播出块。
从技术上讲,还可以允许未检查异常从静态初始化块1传播出去。但是故意这样做是一个非常糟糕的主意!问题是 JVM 本身捕获了未经检查的异常,并将其包装并重新抛出为 ExceptionInInitializerError。
注意:ExceptionInInitializerError 是 Error 不是常规异常。您不应该尝试从中恢复。
在大多数情况下,无法捕获异常:
public class Test {
static {
int i = 1;
if (i == 1) {
throw new RuntimeException("Bang!");
}
}
public static void main(String[] args) {
try {
// stuff
} catch (Throwable ex) {
// This won't be executed.
System.out.println("Caught " + ex);
}
}
}
$ java Test
Exception in thread "main" java.lang.ExceptionInInitializerError
Caused by: java.lang.RuntimeException: Bang!
at Test.<clinit>(Test.java:5)
您无法在上面放置try ... catch 来捕捉ExceptionInInitializerError2。
在某些情况下,您可以抓住它。例如,如果您通过调用 Class.forName(...) 触发了类初始化,则可以将调用包含在 try 中并捕获 ExceptionInInitializerError 或后续的 NoClassDefFoundError。
但是,如果您尝试从ExceptionInInitializerError 中恢复,您可能会遇到障碍。问题是在抛出错误之前,JVM 将导致问题的类标记为“失败”。你根本无法使用它。此外,任何其他依赖于失败类的类在尝试初始化时也将进入失败状态。唯一的出路是卸载所有失败的类。这可能对于动态加载的代码是可行的3,但一般情况下并非如此。
1 - 如果一个静态块无条件抛出一个未经检查的异常,这是一个编译错误。
2 - 您可能可以通过注册一个默认的未捕获异常处理程序来拦截它,但这不会让您恢复,因为您的“主”线程无法启动。
3 - 如果你想恢复失败的类,你需要摆脱加载它们的类加载器。
这个设计决定背后的原因是什么?
是为了保护程序员不写出抛出无法处理的异常的代码……因为程序员没有办法写handler。
正如我们所见,静态初始化程序中的异常会将典型应用程序变成砖块。语言设计人员可以帮助程序员的最好的事情是指定检查的案例 1 是编译错误。不幸的是,对未经检查的异常也这样做是不切实际的。
好的,那么如果您的代码“需要”在静态初始化程序中抛出异常,您应该怎么做。基本上,有两种选择: