当您可以捕获Throwables时,为什么还要捕获Java中的异常?


67

最近,我们在Java服务器应用程序中遇到了一个问题,该应用程序正在抛出未被捕获的错误,因为Error是Throwable的一个单独的子类,而我们仅捕获了Exceptions。

我们通过捕获Throwables而不是Exceptions解决了紧迫的问题,但是这让我开始思考为什么您要捕获Exceptions而不是Throwables,因为那样您会错过Errors。

那么,当您可以捕获Throwables时为什么还要捕获异常



7
我不认为这是重复的。另一个问题是关于扔掉它们,而不是抓住它们。
迈克尔·迈尔斯

2
在某些情况下可能会捕获错误。例如,如果您要构建一个资源为JVM的系统。我想知道WL中的受管服务器是否确实捕获了此类异常,只是为了通知管理器节点并让其重新启动。但是,这些不是高级主题吗?
OscarRyz

好吧,即使捕获到异常也不是一个好主意:stackoverflow.com/questions/2416316/…–
Raedwald

取决于情况,有时您确实需要捕获错误并帮助您确定根本原因。
巴吉塔

Answers:


61

捕获错误后,这完全取决于您将如何处理错误。通常,捕获错误可能不应被视为“正常”异常流的一部分。如果您确实抓住了一个,那么您就不应该考虑“好像什么也没发生一样继续进行”,因为JVM(和各种库)将使用Errors来表示“确实发生了一些严重的事情,我们需要尽快关闭”。通常,最好在他们告诉您即将结束时听他们讲。

另一个问题是,是否可以从错误中恢复,可能取决于特定的虚拟机,而您可能无法控制。

就是说,在一些极端的情况下,安全和/或希望捕获Error或至少某些子类是可取的:

  • 在某些情况下,您确实确实希望停止正常的流程:例如,如果您在Servlet中,则可能不希望Servlet运行器的默认异常处理程序向世界宣布您遇到了OutOfMemoryError,无论是还是不是,你可以从中恢复过来。
  • 有时,如果JVM可以从错误原因中完全恢复,则将抛出错误。例如,如果尝试分配数组时发生OutOfMemoryError,至少在Hotspot中,看来可以安全地从中恢复。(当然,在其他情况下,可能会抛出OutOfMemoryError,这是不安全的尝试尝试。)

因此,最重要的是:如果您确实捕获了Throwable / Error而不是Exception,那应该是一个明确定义的情况,您知道自己在“做一些特别的事情”

编辑:可能这很明显,但是我忘了说实际上,JVM可能实际上不会在Error上调用您的catch子句。我肯定已经看到Hotspot在捕获某些OutOfMemoryErrors和NoClassDefFoundError方面的尝试很轻松。


如果我抓到(Throwable t),将其记录下来并扔回去会不会有问题?
安迪·杜弗雷斯

本身不行-问题更多是后勤问题(您可能会多次记录相同的异常-这令人困惑吗?必须将封闭方法声明为Throwable Throwable ...)。但是VM本身并不关心这些问题。
尼尔·科菲,2014年

62

从Java API文档中:

该类Exception及其子类是Throwable表示合理应用程序可能希望捕获的条件的。

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

错误通常是低级错误(例如,由虚拟机引发的错误),不应被应用程序捕获,因为可能无法进行合理的延续。


8
您可以记录该消息以显示应用程序为何死亡。
罗布·梅休

2
捕获Throwable时要做的一件合理的事情可以是记录“为什么发生”。但是问题是,记录器实际上是否可用,比如说在VirtualMachineError上?是否有一个Javadoc明确声明了一个安全列表-100%不可恢复的错误,100%可恢复的错误和模糊状态错误?
扇区

例如,在使用改造时,如果网络连接失败会触发android中的错误,该怎么办?在这种情况下,您应该捕获错误或异常吗?
杰里·奥卡福

6

通常,错误是您无法从中恢复的问题,例如OutOfMemoryError。捕获它们无事可做,因此通常应让它们逃脱并关闭虚拟机。


2
通常,我会在主线程中捕获Throwable,因此我尤其可以捕获此错误。它无能为力,但是您可以记录下来。否则,除非您查看STDERR,否则可能不会看到该错误。
克里斯·戴尔

1
如果您没有足够的内存来记录该怎么办?您这样做有什么好处?
MetroidFan2002

4
@ MetroidFan2002:在很多情况下,您确实有足够的内存。例如,如果要将大文件加载到ram中,分配可能会失败。然后,您可以以友好的方式通知用户。但在这种情况下,我建议捕获OutOfMemoryError而不是Throwable。
Shiny先生和新安宇

@ MetroidFan2002如果我错了,请有人纠正我,但是我相信JVM(也许只是从Java 6开始,不确定)会保留并保证在这种情况下用于记录的少量内存。
亚历克斯比尔兹利

@所有。仍然没有好的方法来捕捉Throwable。根据应用程序的性质,该日志可能已由主机系统记录。
OscarRyz

6

许多其他答案都过于狭things地看待事物。

正如他们所说,如果您正在编写应用程序代码,则不应捕获Throwable。您对此无能为力,因此最好让周围的系统(JVM或框架)处理这些问题。

但是,如果您正在编写“系统代码”(例如框架或其他低级代码),那么您可能很想抓住Throwable。原因是尝试报告异常,可能是在日志文件中。在某些情况下,您的日志记录将失败,但在大多数情况下,它将成功,并且您将拥有解决问题所需的信息。尝试进行日志记录后,应该重新抛出,终止当前线程或退出整个JVM。


5

我会走一条与其他人略有不同的路线。

在很多情况下,您想捕获Throwable(主要是记录/报告发生了恶事)。

然而,您需要小心并丢弃所有您无法处理的东西。

ThreadDeath尤其如此。

如果您遇到Throwable,请确保执行以下操作:

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

除非不声明“ throwable Throwable”,否则您不能抛出Throwable !!
劳伦斯·多尔

否,但是您可以检查Error的Throwable实例,然后在不声明的情况下强制转换并重新抛出该实例。这是从Future.get()返回的ExecutionException的标准做法
noahlz,2009年

并且可以根据需要将throwable包装为运行时异常(或其子类)
Scott Stanchfield,2009年

3

在至少一种情况下,我认为您可能必须捕获一个抛出异常或通用异常-如果您正在运行一个单独的线程来执行任务,则可能想知道该线程的“运行”方法是否已捕获了某些异常是否例外。在这种情况下,您可能会执行以下操作:


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

我真的不确定这是否是最好的方法,但是它有效。而且我有一个由JVM引发的“ ClassNotFound”错误,这是一个错误,而不是异常。如果我抛出异常,则我不确定如何在调用线程中捕获该异常(可能有一个方法,但我不知道-到目前为止)。

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


1
现在回答这个问题并搜索文档,我刚刚找到了静态方法Thread.UncaughtExceptionHandler。
拉维·瓦劳

但是,总的来说,这更干净,更明显。未捕获的异常处理程序适用于您无法控制run()方法的情况。但是,raview应该修改回答说“至少有一个案例……”
Lawrence Dol

我同意SwMnk。如果您在设计类时期望会引发ClassNotFound错误,那么您应该在线程内处理该异常,并向其客户端提供一种了解发生错误的方法。如果那是因为您没有正确配置环境而发生的,那就是PEBKAC
OscarRyz

3

这篇文章不会使“检查异常很糟糕”的人高兴。但是,我要根据的答案是如何按照创建该语言的人的定义使用Java异常。

快速参考图:

  • 可抛出-永远不要抓住这个
  • 错误-表示VM错误-永不赶上
  • RuntimeException-指示程序员错误-切勿捕获此错误
  • 例外-永远不要抓住这个

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

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

上述“规则”有例外情况(无双关语):

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

对于第二个,通常将主代码,事件处理代码和带有捕获的线程包装到Throwable中,然后检查异常的实际类型并酌情进行处理就足够了。


我认为您需要更清楚一点。NumberFormatException是RuntimeException,而且很明显,这很容易被抓住……
Neil Coffey

我认为他的意思是更高的级别,因为您不知道如何恢复。可以在希望看到的地方捕获它。
sfossen

1
NumberFormatException是一种特殊情况...在制作时可能会出错。但是,使用正则表达式很容易检查它是否为数字或不是正数。Integer.parseInt的一般想法是,您不会传递错误的数据。
TofuBeer

1
考虑更多有关parseInt的问题...鉴于NumberFormat是RuntimeException,因此Integer类的失败是没有公共静态布尔isInt(String)方法。
TofuBeer

不,他们做对了。不要使用正则表达式进行错误检查!您最终会遇到一些极端情况,即与解析代码的行为不匹配,并且几乎找不到该错误。继续并捕获NumberFormatException,这就是它的用途。只是不要做一个全面的“ catch RuntimeException”。
rwallace

2

永远不要捕捉Throwable或,Error并且您通常也不应简单地捕捉通用的ExceptionError通常,大多数合理的程序都无法从中恢复。如果您知道发生了什么,则可以从一个特定的错误中恢复,但是在这种情况下,您应该捕获一个特定的错误,而不是一般的所有错误。

一个不被抓住的好理由Error是因为ThreadDeathThreadDeath从理论上讲,这是一个相当正常的事件,可以从任何地方抛出(其他进程(如JVM本身也可以生成它)),并且它的全部目的是杀死线程。 ThreadDeath显然是一个,Error而不是Exception因为太多的人抓住了所有Exception。如果要抓住ThreadDeath,则必须将其重新抛出,以使线程实际终止。

如果您拥有对源代码的控制权,则可能应该对其进行重组以抛出Exception而不是Error。如果不这样做,您可能应该致电供应商并投诉。 Errors仅应保留为终端的东西,无法从它们中恢复。


这是不可侵犯的。即使您只是捕获日志并重新抛出错误,也不能保证您确实有记录错误的资源,并且不重新抛出错误也是射击自己的最光荣方法之一。如果必须,则捕获一个特定的错误,但不要捕获所有错误。
詹姆斯,

另一方面,如果您在子线程上并且允许Error未被捕获,则它将(可能)被静默忽略……这比捕获并记录错误更令人担忧。
Stephen C

1

通常,在编程时,您应该只捕获特定的异常(例如IOException)。在许多程序中,您可以看到一个非常高级的

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

映入所有错误可能是可恢复的,所有那些表明你的代码,例如一个bug InvalidArgumentExceptionNullPointerException。然后,由于JavaVM本身仍可以正常工作,因此您可以自动发送电子邮件,显示消息框或任何您喜欢的内容。

源自的一切都是Error非常糟糕的事情,您无能为力。问题是,抓住aOutOfMemoryError或a是否有意义VirtualMachineError。(这是JavaVM本身的错误,可能您甚至无法显示消息框或发送电子邮件)

您可能不Error应该从派生类,而应该从Exception或派生RuntimeException


1

我知道这可能是反直觉的,但仅仅是因为你可以捕捉各种异常和将Throwable和错误并不能意味着你应该。

过度激进地捕获java.lang.Exception可能导致应用程序中出现一些严重的错误-因为意外的异常不会冒出来,在开发/测试期间也不会被捕获,等等。

最佳做法:只抓

  1. 您可以处理的异常
  2. 捕获必要的异常

1

通常,尝试仅在可以正确报告错误的情况下尝试捕获错误。

但是,我认为在某些情况下,捕获错误而不报告错误是适当的。我指的是UnsatisfiedLinkError。在JAI中,出于性能原因,该库使用某些本机库来实现大多数运算符,但是,如果该库无法加载(不存在,格式错误,不受支持的平台),该库仍将运行,因为它将退回到仅Java模式。



0

为什么不全部抓住呢?然后将它们记录下来,至少您知道自己有错误。因此,比仅使用异常更好地捕获Throwable / s。


-1

捕获Error没有意义。

错误用于指示您的应用程序确实出了什么问题,应该重新启动它。

例如,一个常见错误是

java.lang.OutOfMemoryError

NOTHING当发生这种情况,你可以做。为时已晚,JVM已经用尽所有选项来获得更多内存,但这是不可能的。

请参阅此其他答案,以进一步了解这三种异常


3
我花了一个多小时来调试应用程序,因为我忘记了执行器不会记录从Runnables传播的错误。我的代码现在可以捕获,记录并重新抛出。所以我不同意!
oxbow_lakes

感谢您的评论。这是什么错误?之后是否可以保持程序运行?
OscarRyz

@OscarRyz错误(如NoSuchMethodError)是由于不兼容的jar
Amareswar 2014年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.