最近,我们遇到了一个 Java 服务器应用程序抛出错误的问题,因为 Error 是 Throwable 的一个独立子类,我们只捕获异常。
我们通过捕获 Throwables 而不是 Exception 解决了当前的问题,但这让我思考为什么要捕获 Exception 而不是 Throwables,因为这样就会错过错误。
那 既然可以捕捉 Throwables,为什么还要捕捉 Exception呢?
来自 JavaAPI 文档:
类 Exception及其子类是 Throwable的一种形式,它指示合理的应用程序可能想要捕获的条件。 Error是 Throwable的一个子类,它指示一个合理的应用程序不应该试图捕捉的严重问题。
类 Exception及其子类是 Throwable的一种形式,它指示合理的应用程序可能想要捕获的条件。
Exception
Throwable
Error是 Throwable的一个子类,它指示一个合理的应用程序不应该试图捕捉的严重问题。
Error
错误通常是低级别的(例如,由虚拟机引发) ,应用程序不应该捕获这些错误,因为合理的延续可能是不可能的。
通常错误是您不可能从中恢复的问题,比如 输出记忆错误。抓住它们无济于事,所以通常应该让它们逃跑,并关闭虚拟机。
通常在编程时,应该只捕获特定的异常(如 IOException)。在许多程序中,你可以看到一个非常顶级的
IOException
try { ... } catch(Exception e) { ... }
它捕获所有可以恢复的错误和所有指示代码中存在 bug 的错误,例如 InvalidArgumentException、 NullPointerException。然后,您可以自动发送电子邮件,显示消息框或任何您喜欢的,因为 JavaVM 本身仍然工作良好。
InvalidArgumentException
NullPointerException
从 Error得到的一切都是非常糟糕的东西,你不能做任何反对。问题是,捕获 OutOfMemoryError或 VirtualMachineError是否有意义。(这是 JavaVM 本身的一个错误,可能你甚至不能显示一个消息框或发送一封电子邮件)
OutOfMemoryError
VirtualMachineError
您可能不应该从 Error派生类,而应该从 Exception或 RuntimeException派生。
RuntimeException
稍微偏离了主题,但是您可能还想了解 这篇非常好的文章关于异常的内容。
这一切都取决于你如何处理一个错误,一旦你抓住了它。一般来说,捕捉错误可能不应该被视为“正常”异常流的一部分。如果你确实发现了一个错误,你不应该考虑“像什么都没有发生一样继续下去”,因为 JVM (和各种库)会使用 Error 作为一种信号,表明“真正严重的事情发生了,我们需要尽快关闭”。一般来说,当他们告诉你末日将至的时候,最好还是听他们的。
另一个问题是,错误的可恢复性与否可能取决于特定的虚拟机,而虚拟机是您可以控制或不能控制的。
也就是说,在一些极端情况下,捕获 Error (或者至少某些子类)是安全和/或可取的:
所以底线是: 如果您确实捕获了 Throwable/Error 而不是 Exception,那么它应该是 定义明确的情况下,你知道你在“做一些特别的事情”。
编辑: 可能这是显而易见的,但我忘了说,在实践中,JVM 实际上可能不会调用 catch 子句对一个错误。我确实见过 Hotspot 巧妙地掩盖了捕捉某些 OutOfMemory 错误和 NoClassDefFoundError 的尝试。
我知道这可能有违直觉,但正因为您可以捕获各种异常和抛物线及错误,所以 没有意味着您应该这样做。
过于积极地捕捉 java.lang。异常可能导致应用程序中出现一些严重的错误——因为意外的异常从来不会冒泡,在开发/测试过程中从来不会被捕获,等等。
最佳实践: 只接球
不要 永远捕捉 Throwable或 Error,你一般也不应该简单地捕捉一般的 Exception。Error通常是大多数合理的程序不可能从中恢复的东西。如果您知道正在发生什么,您可能能够从一个特定的错误中恢复,但是在这种情况下,您应该捕获 只有那个特定的错误,而不是一般的所有错误。
一个很好的理由不赶上 Error是因为 ThreadDeath。ThreadDeath是一个相当正常的事件,理论上可以从任何地方抛出(其他进程,如 JVM 本身可以生成它) ,它的全部意义就是杀死线程。显然,ThreadDeath是 Error而不是 Exception,因为太多的人捕捉所有的 Exception。如果你曾经抓住 ThreadDeath,你 必须的重新抛出它,使你的线程实际上死亡。
ThreadDeath
如果您对源代码有控制权,那么它可能应该被重新构造为抛出 Exception而不是 Error。如果你不这样做,你可能应该打电话给供应商并投诉。Error应该只保留给那些终端的、无法从中恢复的东西。
我会走一条与众不同的路线。
在许多情况下,您希望捕捉 Throwable (主要是为了记录/报告发生了不好的事情)。
但是 ,您需要小心并重新抛出您无法处理的任何内容。
ThreadDeath 尤其如此。
如果你曾经抓到过 Throwable,一定要做到以下几点:
try { ... } catch (SomeExceptionYouCanDoSomethingWith e) { // handle it } catch (ThreadDeath t) { throw t; } catch (Throwable t) { // log & rethrow }
这篇文章不会让“被检查的异常是坏的”的人高兴。然而,我的答案是基于 Java 异常是如何被创建语言的人定义的。
快速参考图表:
不应该捕获 Exception 的原因是它捕获所有子类,包括 RuntimeException。
您不应该捕获 Throwable 的原因是它捕获了所有的子类,包括 Error 和 Exception。
上述“规则”也有例外(并非双关语) :
对于第二个异常,通常只需要将 main、事件处理代码和线程包装到 Throwable,然后检查异常的实际类型并适当地处理它。
至少有一种情况下,我认为你可能必须捕获一个可抛出的或一般的异常-如果你正在运行一个单独的线程来执行任务,你可能想知道如果“运行”方法的线程已经捕获了一些异常或没有。在这种情况下,您可能会这样做:
public void run() { try { ... } catch(Throwable t) { threadCompletionError = t; } }
我真的不确定这是否是最好的方法,但它确实有效。JVM 引发了一个“ ClassNotfound”错误,这是一个错误,而不是一个异常。如果我让异常被抛出,我不确定如何在调用线程中捕获它(可能有一个方法,但我还不知道它)。
至于 ThreadDeath 方法,不要调用“ Thread.stop ()”方法。调用 Thread.stop 并让线程检查它是否被某人中断。
一般来说,如果能够正确地报告错误,那么尝试捕获错误是合理的。
但是,我相信在某些情况下,捕获一个错误而不报告它是合适的。我指的是不满意的 LinkError。在 JAI 中,由于性能原因,库使用一些本地库来实现大多数操作符,但是如果库无法加载(不存在,格式错误,不支持平台) ,库仍然可以运行,因为它将回落到只支持 Java 的模式。
抓住 错误是没有意义的。
错误用于指示应用程序中出现了严重错误,应该重新启动它。
例如,一个常见的错误是
java.lang.OutOfMemoryError
当这种情况发生时,你可以做 没什么。已经太晚了,JVM 已经用尽了所有的选项来获得更多的内存,但这是不可能的。
请参阅另一个答案以了解关于 三种例外的更多信息。
很多其他的答案都太狭隘了。
正如他们所说,如果您正在编写应用程序代码,就不应该捕获 Throwable。您对此无能为力,因此最好允许周围的系统(JVM 或框架)来处理这些问题。
但是,如果您正在编写“系统代码”,比如框架或其他低级代码,那么您可能非常希望捕获 Throwable。原因是要 尝试报告异常,可能是在一个日志文件中。在某些情况下,您的日志记录会失败,但在大多数情况下,它会成功,您将获得解决问题所需的信息。完成日志记录后,应该重新抛出、终止当前线程或退出整个 JVM。
为什么不把他们都抓起来?然后记录它们,至少你知道你有一个错误。所以抓住 Throwable/s 比抓住 Exception/s 要好。