为什么在Java中捕获exception,什么时候可以捕获Throwables?

我们最近遇到了一个Java服务器应用程序的问题,其中的应用程序抛出的错误没有被捕获,因为Error是Throwable的一个单独的子类,我们只捕获exception。

我们通过捕获Throwables而不是Exception来解决当前的问题,但是这让我想到了为什么你会想要捕捉exception,而不是Throwables,因为你会错过错误。

那么, 为什么要捕获exception呢,当你能捕获Throwables呢?

这一切都取决于一旦你发现错误,你将要做什么。 一般来说,捕捉错误可能不应被视为您的“正常”exceptionstream程的一部分。 如果你确实抓住了一个,那么你不应该考虑“继续进行,好像什么也没有发生”,因为JVM(和各种库)会使用错误来表示“事情真的发生了,我们需要尽快closures“。 一般来说,当他们告诉你最后的结果时,最好听一听。

另一个问题是,错误的可恢复性或不可能取决于特定的虚拟机,这是您可能或不能控制的东西。

也就是说,有几个angular落案例可以安全和/或理解错误,或者至less是某些子类:

  • 有些情况下,你确实想停止正常的stream程:例如,如果你在一个Servlet中,你可能不希望Servlet运行器的默认exception处理程序向全世界宣布你已经有一个OutOfMemoryError,无论是不是你可以从中恢复。
  • 有时,如果JVM可以从错误原因中彻底恢复,则会引发错误。 例如,如果尝试分配数组时发生OutOfMemoryError,至less在热点中,似乎可以安全地从此恢复。 (当然还有其他一些情况,如果试图犁地,可能会抛出OutOfMemoryError。)

所以底线是:如果你捕获Throwable / Error而不是Exception,它应该是一个明确定义的情况,你知道你在做“特别的事情”

编辑:可能这是显而易见的,但我忘了说,在实践中, JVM可能不会真正调用你的catch子句的错误。 我已经明确地看到热点试图捕捉某些OutOfMemoryErrors和NoClassDefFoundError。

从Java API文档:

Exception和它的子类是一个Throwable的forms,表示一个合理的应用程序可能想要捕获的条件。

一个ErrorThrowable一个子类,表示一个合理的应用程序不应该试图捕捉的严重问题。

错误通常是低级的(例如,由虚拟机产生),并且不应该被应用程序捕获,因为可能无法实现合理的连续性。

通常,错误是您无法从中恢复的问题,例如OutOfMemoryError 。 抓住它们是无关紧要的,所以你通常应该让它们逃脱,并把虚拟机关掉。

其他许多答案都是把事情看得太狭隘。

正如他们所说,如果您正在编写应用程序代码,则不应捕获Throwable。 你不能做任何事情,所以允许周围的系统(JVM或框架)来处理这些问题是最好的。

但是,如果您正在编写“系统代码”,如框架或其他低级代码,那么您可能很想捕获Throwable。 原因是试图报告exception,可能在日志文件中。 在某些情况下,您的日志logging将失败,但在大多数情况下,它会成功,您将获得解决问题所需的信息。 一旦你做了你的日志logging尝试,你应该重新抛出,杀死当前线程,或退出整个JVM。

我会和其他人稍有不同。

有很多情况下,你会想赶Throwable(主要是logging/报告发生了一些邪恶事件)。

但是 ,你需要小心,重新抛出任何你无法处理的东西。

ThreadDeath尤其如此。

如果您遇到Throwable,请务必执行以下操作:

 try { ... } catch (SomeExceptionYouCanDoSomethingWith e) { // handle it } catch (ThreadDeath t) { throw t; } catch (Throwable t) { // log & rethrow } 

不要捕获ThrowableError ,你通常不应该简单地捕获一个通用的ExceptionError通常是大多数合理的程序不可能从中恢复的东西。 如果你知道发生了什么事情,你可以从一个特定的错误中恢复,但是在这种情况下,你应该捕获一个特定的错误,而不是一般的错误。

不抓Error一个好理由是因为ThreadDeathThreadDeath是一个相当正常的事件,理论上可以从任何地方(像JVM本身可以产生它的其他进程)抛出,而且它的全部要点是杀死你的线程。 ThreadDeath明确地是一个Error而不是一个Exception因为太多人抓住所有Exception 。 如果你曾经抓过ThreadDeath ,你必须重新抛出它,以便你的线程死亡。

如果你有源代码的控制权,它可能应该被重构为抛出一个Exception而不是一个Error 。 如果你不这样做,你应该打电话给供应商并投诉。 Error应该只保留为terminal的东西,不可能从它们中恢复。

至less有一种情况,我认为你可能需要捕获一个throwable或一个普通的exception – 如果你正在运行一个单独的线程来执行一个任务,你可能想知道线程的“run”方法是否抓住了一些例外与否。 在这种情况下,你可能会这样做:

 public void run() { try { ... } catch(Throwable t) { threadCompletionError = t; } } 

我真的不确定这是否是最好的方法,但它是有效的。 而且我有一个由JVM引发的“ClassNotFound”错误,这是一个错误,而不是一个例外。 如果我让exception抛出,我不知道如何在调用线程中捕捉它(可能有一种方法,但我不知道 – )。

至于ThreadDeath方法,不要调用“Thread.stop()”方法。 调用Thread.interrupt并让你的线程检查是否被某人中断。

我知道这可能是违反直觉,但只是因为你可以捕捉各种exception和Throwables和错误并不意味着你应该。

过度主动地捕获java.lang.Exception可能会导致应用程序中出现严重的错误 – 因为意外的exception从不起泡,在开发/testing过程中永远不会被捕获

最佳做法:只抓

  1. 您可以处理的例外情况
  2. 必须抓住的例外情况

一般来说,如果只是为了能够正确地报告而试图捕捉错误是合理的。

但是,我相信有些情况下,可能会发现错误而不报告。 我指的是UnsatisfiedLinkError。 在JAI中,库使用一些本地库来实现大多数操作符,但是如果库无法加载(不存在,错误的格式,不支持的平台),库仍然会运行,因为它会回落到仅Java模式。

这篇文章不会让“检查exception不好”的人高兴。 然而,我的答案是,Javaexception是如何被创build语言的人定义的。

快速参考图表:

  • 可扔 – 永远不要抓住这个
  • 错误 – 表示虚拟机错误 – 永远不要抓住这个
  • RuntimeException – 表示程序员错误 – 从来没有捕捉到这一点
  • 例外 – 永远不要抓住这个

你不应该捕获Exception的原因是它捕获了所有的子类,包括RuntimeException。

你不应该捕获Throwable的原因是它捕获了所有的子类,包括Error和Exception。

上述“规则”有例外(无双关意):

  • 您正在使用的代码(来自第三方)会抛出Throwable或Exception
  • 您正在运行不可信的代码,如果发生exception,可能会导致程序崩溃。

对于第二种情况,通常只要将主要事件处理代码和线程与catch一起包装为Throwable就足够了,然后检查exception的实际types并根据需要进行处理。

通常在编程时,应该只捕获一个特定的exception(如IOException )。 在很多程序中,你可以看到一个顶层

 try { ... } catch(Exception e) { ... } 

捕获所有可以恢复的错误以及所有代码中的错误,例如InvalidArgumentExceptionNullPointerException 。 然后,您可以自动发送一个电子邮件,显示一个消息框或任何你喜欢的,因为JavaVM本身仍然工作正常。

Error派生的一切都是非常糟糕的,你不能做任何事情。 问题是,如果有意义的话赶上一个OutOfMemoryErrorVirtualMachineError 。 (这是JavaVM本身的错误,可能你甚至不能显示消息框或发送电子邮件)

你可能不应该从Error派生类,你应该从ExceptionRuntimeException派生。

稍微偏离主题,但你也可以看看这个关于例外的非常好的文章 。

为什么不抓住他们呢? 然后login他们,至less你知道你有一个错误。 所以更好地捕获Throwable / s比Exception / s只。

捕捉错误没有意义。

错误是用来表示在你的应用程序中发生了一些错误,应该重新启动。

例如,一个常见的错误是

 java.lang.OutOfMemoryError 

当发生这种情况时你无能为力。 已经太晚了,JVM已经耗尽了所有的选项来获得更多的内存,但是这是不可能的。

看到这个其他的答案,了解更多关于这三种例外 。