为什么在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,表示一个合理的应用程序可能想要捕获的条件。一个
Error
是Throwable
一个子类,表示一个合理的应用程序不应该试图捕捉的严重问题。
错误通常是低级的(例如,由虚拟机产生),并且不应该被应用程序捕获,因为可能无法实现合理的连续性。
通常,错误是您无法从中恢复的问题,例如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 }
不要捕获Throwable
或Error
,你通常不应该简单地捕获一个通用的Exception
。 Error
通常是大多数合理的程序不可能从中恢复的东西。 如果你知道发生了什么事情,你可以从一个特定的错误中恢复,但是在这种情况下,你应该只捕获一个特定的错误,而不是一般的错误。
不抓Error
一个好理由是因为ThreadDeath
。 ThreadDeath
是一个相当正常的事件,理论上可以从任何地方(像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过程中永远不会被捕获
最佳做法:只抓
- 您可以处理的例外情况
- 必须抓住的例外情况
一般来说,如果只是为了能够正确地报告而试图捕捉错误是合理的。
但是,我相信有些情况下,可能会发现错误而不报告。 我指的是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) { ... }
捕获所有可以恢复的错误以及所有代码中的错误,例如InvalidArgumentException
, NullPointerException
。 然后,您可以自动发送一个电子邮件,显示一个消息框或任何你喜欢的,因为JavaVM本身仍然工作正常。
从Error
派生的一切都是非常糟糕的,你不能做任何事情。 问题是,如果有意义的话赶上一个OutOfMemoryError
或VirtualMachineError
。 (这是JavaVM本身的错误,可能你甚至不能显示消息框或发送电子邮件)
你可能不应该从Error
派生类,你应该从Exception
或RuntimeException
派生。
稍微偏离主题,但你也可以看看这个关于例外的非常好的文章 。
为什么不抓住他们呢? 然后login他们,至less你知道你有一个错误。 所以更好地捕获Throwable / s比Exception / s只。
捕捉错误没有意义。
错误是用来表示在你的应用程序中发生了一些错误,应该重新启动。
例如,一个常见的错误是
java.lang.OutOfMemoryError
当发生这种情况时你无能为力。 已经太晚了,JVM已经耗尽了所有的选项来获得更多的内存,但是这是不可能的。
看到这个其他的答案,了解更多关于这三种例外 。