为什么 Java 不允许从静态初始化块中抛出已检查的异常?
为什么 Java 不允许从静态初始化块中抛出已检查的异常?这个设计决定背后的原因是什么?
Why doesn't Java allow to throw a checked exception from a static initialization block? What was the reason behind this design decision?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(8)
因为在您的源中不可能处理这些已检查的异常。您对初始化过程没有任何控制权,并且无法从源中调用 static{} 块,因此您可以用 try-catch 包围它们。
由于无法处理已检查异常指示的任何错误,因此决定禁止抛出已检查异常静态块。
静态块不得抛出已检查异常,但仍允许抛出未检查/运行时异常。但根据上述原因,你也无法处理这些。
总而言之,此限制阻止(或至少使开发人员更难)构建可能导致应用程序无法恢复的错误的东西。
Because it is not possible to handle these checked exceptions in your source. You do not have any control over the initialization process and static{} blocks cannot be called from your source so that you could surround them with try-catch.
Because you cannot handle any error indicated by a checked exception, it was decided to disallow throwing of checked exceptions static blocks.
The static block must not throw checked exceptions but still allows unchecked/runtime-exceptions to be thrown. But according to above reasons you would be unable to handle these either.
To summarize, this restriction prevents (or at least makes it harder for) the developer from building something which can result in errors from which the application would be unable to recover.
您可以通过捕获任何已检查异常并将其作为未检查异常重新抛出来解决该问题。这个未经检查的异常类可以很好地用作包装器:
java.lang.ExceptionInInitializerError
。示例代码:
You can work around the problem by catching any checked exception and rethrowing it as an unchecked exception. This unchecked exception class works well as a wrapper:
java.lang.ExceptionInInitializerError
.Sample code:
它必须看起来像这样(这是不是有效的Java代码),
但是你会在哪里捕获它呢?检查异常需要捕获。想象一些可能初始化类的示例(或者可能不会,因为它已经初始化了),只是为了引起人们对它所引入的复杂性的注意,我将这些示例放在另一个静态初始化器中:
还有另一个令人讨厌的事情 -
想象 ClassA有一个静态初始化程序抛出一个已检查的异常:在这种情况下,MyInterface(这是一个具有“隐藏”静态初始化程序的接口)必须抛出异常或处理它 - 在接口处进行异常处理?最好保持原样。
It would have to look like this (this is not valid Java code)
but how would ad where you catch it? Checked exceptions require catching. Imagine some examples that may initialize the class (or may not because it is already initialized), and just to draw the attention of the complexity of that it would introduce, I put the examples in another static initalizer:
And another nasty thing -
Imagine ClassA had a static initializer throwing a checked exception: In this case MyInterface (which is an interface with a 'hidden' static initializer) would have to throw the exception or handle it - exception handling at an interface? Better leave it as it is.
从技术上讲,你可以做到这一点。但是,必须在块内捕获已检查的异常。
实际的 Java 限制是不允许检查的异常传播到块之外。
从技术上讲,还可以允许未检查异常从静态初始化程序块1传播出去。但故意这样做实在是个坏主意!问题在于 JVM 本身捕获了未经检查的异常,并将其包装并以 ExceptionInInitializerError 的形式重新抛出。
注意:
ExceptionInInitializerError
是一个Error
,而不是常规异常。您不应该尝试从中恢复。大多数情况下,异常无法被捕获:
上面没有地方可以放置
try ... catch
来捕获ExceptionInInitializerError
2。在某些情况下你可以抓住它。例如,如果您通过调用
Class.forName(...)
触发类初始化,则可以将调用包含在try
中并捕获ExceptionInInitializerError
或后续的NoClassDefFoundError
。但是,如果您尝试从
ExceptionInInitializerError
中恢复,您可能会遇到障碍。问题在于,在抛出错误之前,JVM 将导致问题的类标记为“失败”。您根本无法使用它。此外,依赖于失败类的任何其他类如果尝试初始化也将进入失败状态。唯一的方法是卸载所有失败的类。对于动态加载的代码3来说,可能是可行的,但通常情况下并非如此。1 - 如果静态块无条件抛出未经检查的异常,则这是一个编译错误。
2 - 您也许可以通过注册默认的未捕获异常处理程序来拦截它,但这不允许您恢复,因为您的“主”线程无法启动。
3 - 如果您想恢复失败的类,您需要删除加载它们的类加载器。
它是为了保护程序员免于编写抛出无法处理的异常的代码……因为程序员没有办法编写处理程序。
正如我们所看到的,静态初始化程序中的异常会将典型的应用程序变成一块砖。语言设计者可以帮助程序员做的最好的事情就是指定检查的情况 1 是编译错误。不幸的是,对于未经检查的异常也这样做是不切实际的。
如果您的代码“需要”在静态初始化程序中抛出异常,您应该怎么做?
基本上,有两种选择:
如果(完全!)从块内的异常中恢复是可能的,然后这样做。
否则,请重构代码,以便初始化不会发生在静态初始化块(或静态变量的初始值设定项中)。将初始化放在可以从常规线程调用的方法或构造函数中。
Technically, you can do this. However, the checked exception must be caught within the block.
The actual Java restriction is that a checked exception is not allowed to propagate out of the block.
Technically, it is also possible to allow an unchecked exception to propagate out of a static initializer block1. But it is a really bad idea to do this deliberately! The problem is that the JVM itself catches the unchecked exception, and wraps it and rethrows it as a
ExceptionInInitializerError
.NB: that
ExceptionInInitializerError
is anError
not a regular exception. You should not attempt to recover from it.In most cases, the exception cannot be caught:
There is nowhere you can place a
try ... catch
in the above to catch theExceptionInInitializerError
2.In some cases you can catch it. For example, if you triggered the class initialization by calling
Class.forName(...)
, you can enclose the call in atry
and catch either theExceptionInInitializerError
or a subsequentNoClassDefFoundError
.However, if you attempt to recover from an
ExceptionInInitializerError
you are liable to run into a roadblock. The problem is that before throwing the error, the JVM marks the class that caused the problem as "failed". You simply won't be able to use it. Furthermore, any other classes that depend on the failed class will also go into failed state if they attempt to initialize. The only way forward is to unload all of the failed classes. That might be feasible for dynamically loaded code3, but in general it isn't.1 - It is a compilation error if a static block unconditionally throws an unchecked exception.
2 - You might be able to intercept it by registering a default uncaught exception handler, but that won't allow you to recover, because your "main" thread can't start.
3 - If you wanted to recover the failed classes, you would need to get rid of the classloader that loaded them.
It is to protect the programmer from writing code that throws exceptions that cannot be handled ... because there is no way for the programmer to write a handler.
As we have seen, an exception in a static initializer turns a typical application into a brick. The best thing that the language designers can do help the the programmer is to specified that the checked case 1s a compilation error. Unfortunately, it is not practical to do this for unchecked exceptions as well.
What should you do if your code "needs" to throw exceptions in a static initializer"?
Basically, there are two alternatives:
If (full!) recovery from the exception within the block is possible, then do that.
Otherwise, restructure your code so that the initialization doesn't happen in a static initialization block (or in the initializers of static variables). Put the initialization in a method or constructor that can be called from a regular thread.
查看 Java 语言规范 :据说,如果静态初始化器
失败能够突然完成并带有检查异常,则这是一个编译时错误。Take a look at the Java Language Specifications: it is stated that it is a compile time error if static initializer
failsis able to complete abruptly with a checked exception.由于您编写的代码无法调用静态初始化块,因此抛出已检查的
异常
是没有用的。如果可能的话,当抛出检查异常时,jvm 会做什么?运行时异常
向上传播。Since no code you write can call static initialization block, it is not useful to throw checked
exceptions
. If it were possible, what would the jvm do when a checked exceptions are thrown?Runtimeexceptions
are propagated up.例如:Spring 的 DispatcherServlet (org.springframework.web.servlet.DispatcherServlet) 处理捕获一个已检查异常并抛出另一个未检查异常的场景。
For example: Spring's DispatcherServlet (org.springframework.web.servlet.DispatcherServlet) handles the scenario which catches a checked exception and throws another unchecked exception.
我能够编译并抛出一个已检查的异常......
I am able to compile throwing a checked Exception Also....