为什么 Java 不允许从静态初始化块中抛出已检查异常?这个设计决定背后的原因是什么?
8 回答
因为无法在您的源代码中处理这些已检查的异常。您无法控制初始化过程,并且无法从源调用静态{} 块,因此您可以用 try-catch 将它们包围。
由于您无法处理已检查异常指示的任何错误,因此决定禁止抛出已检查异常静态块。
静态块不得抛出已检查异常,但仍允许抛出未检查/运行时异常。但是根据上述原因,您也无法处理这些。
总而言之,此限制阻止(或至少使其更难)开发人员构建可能导致应用程序无法从中恢复的错误的东西。
您可以通过捕获任何已检查的异常并将其作为未检查的异常重新抛出来解决此问题。这个未经检查的异常类可以很好地用作包装器:java.lang.ExceptionInInitializerError
.
示例代码:
protected static class _YieldCurveConfigHelperSingleton {
public static YieldCurveConfigHelper _staticInstance;
static {
try {
_staticInstance = new YieldCurveConfigHelper();
}
catch (IOException | SAXException | JAXBException e) {
throw new ExceptionInInitializerError(e);
}
}
}
它必须看起来像这样(这不是有效的 Java 代码)
// Not a valid Java Code
static throws SomeCheckedException {
throw new SomeCheckedException();
}
但是如何在你捕捉到它的地方做广告呢?已检查的异常需要捕获。想象一些可能初始化类的示例(或者可能不会因为它已经初始化),并且只是为了引起人们注意它会引入的复杂性,我将示例放在另一个静态初始化器中:
static {
try {
ClassA a = new ClassA();
Class<ClassB> clazz = Class.forName(ClassB.class);
String something = ClassC.SOME_STATIC_FIELD;
} catch (Exception oops) {
// anybody knows which type might occur?
}
}
还有一件讨厌的事——
interface MyInterface {
final static ClassA a = new ClassA();
}
想象一下 ClassA 有一个静态初始化程序抛出一个已检查的异常:在这种情况下,MyInterface(它是一个带有“隐藏”静态初始化程序的接口)必须抛出异常或处理它——在接口处进行异常处理?最好保持原样。
为什么 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)
您无法在上面放置 atry ... catch
来捕捉ExceptionInInitializerError
2。
在某些情况下,您可以抓住它。例如,如果您通过调用来触发类初始化Class.forName(...)
,则可以将调用包含在 a 中try
并捕获 theExceptionInInitializerError
或后续NoClassDefFoundError
。
然而,如果你试图从一个问题中恢复过来ExceptionInInitializerError
,你很可能会遇到障碍。问题是在抛出错误之前,JVM 将导致问题的类标记为“失败”。你根本无法使用它。此外,任何其他依赖于失败类的类在尝试初始化时也将进入失败状态。唯一的出路是卸载所有失败的类。这对于动态加载的代码3可能是可行的,但总的来说并非如此。
1 - 如果静态块无条件地抛出未经检查的异常,则为编译错误。
2 - 您可以通过注册一个默认的未捕获异常处理程序来拦截它,但这不会让您恢复,因为您的“主”线程无法启动。
3 - 如果你想恢复失败的类,你需要摆脱加载它们的类加载器。
这个设计决定背后的原因是什么?
它是为了保护程序员不编写抛出无法处理的异常的代码……因为程序员没有办法编写处理程序。
正如我们所见,静态初始化程序中的异常会将典型应用程序变成砖块。语言设计人员可以帮助程序员的最好的事情是指定检查的案例 1 是编译错误。不幸的是,对未经检查的异常也这样做是不切实际的。
好的,那么如果您的代码“需要”在静态初始化程序中引发异常,您应该怎么做。基本上,有两种选择:
如果(完全!)从块内的异常中恢复是可能的,那么就这样做。
否则,重构您的代码,以便初始化不会发生在静态初始化块(或静态变量的初始化程序)中。将它放在可以从常规线程调用的方法或构造函数中。
看一下Java 语言规范:如果静态初始化程序失败 能够突然完成并带有检查的异常,则说明这是一个编译时错误。
由于您编写的任何代码都不能调用静态初始化块,因此 throw checked 没有用exceptions
。如果可能,当抛出检查异常时 jvm 会做什么?Runtimeexceptions
被向上传播。
例如:Spring 的 DispatcherServlet (org.springframework.web.servlet.DispatcherServlet) 处理捕获已检查异常并抛出另一个未检查异常的场景。
static {
// Load default strategy implementations from properties file.
// This is currently strictly internal and not meant to be customized
// by application developers.
try {
ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class);
defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
}
catch (IOException ex) {
throw new IllegalStateException("Could not load '" + DEFAULT_STRATEGIES_PATH + "': " + ex.getMessage());
}
我能够编译抛出一个检查异常也......
static {
try {
throw new IOException();
} catch (Exception e) {
// Do Something
}
}