抛出异常或使用try-catch语句

83

在决定是否要添加throws子句或使用try-catch时,有哪些一般性的经验法则呢?

从我自己所读的内容来看,在调用者违反了其端的合约(传递对象)时应使用throws,而在方法内部执行过程中发生异常时应使用try-catch。这样做正确吗?如果是这样的话,那么调用方应该怎么做?

P.S: 在Google和SO上搜索过,但希望能得到一个清晰的答案。


6
我一直是“如果在这里处理它是有意义的...那么就去处理它”的粉丝。 - CheesePls
我喜欢在异常发生的地方处理所有的异常,这样我就不必担心以后会出现什么问题。 - Doug Hauf
11个回答

66
  • 只有在能够有意义地处理异常的情况下才捕获它
  • 如果当前方法的使用者需要处理异常,则向上声明抛出异常
  • 如果异常是由输入参数引起的(但这些通常是未经检查的异常),则抛出异常

第三条规则非常明确。关于以有意义的方式处理异常,这是在程序的较高层面吗?这是否意味着API通常会抛出异常?此外,在什么情况下应该重新抛出来自子方法(例如来自私有实用方法)的异常? - James P.
嗯,这在很大程度上取决于情况。API会抛出异常,没错。但接下来就涉及到选择是使用"已检查异常"还是"未检查异常"。 - Bozho
3
为什么要重新抛出异常?只需不捕获它并将其传递到调用栈上方即可。 - Steve Kuo

15
一般而言,当方法无法在本地处理相关问题时,应向调用者抛出异常。例如,如果该方法应从给定路径读取文件,则无法以明智的方式在本地处理 IOExceptions。对于无效输入也同样适用,但在这种情况下,我个人的选择是抛出未经检查的异常,例如IllegalArgumentException
同时,当以下条件之一满足时,它应该捕获来自所调用方法的异常:
- 它是可以在本地处理的内容(例如尝试将输入字符串转换为数字,如果转换失败,则完全可以返回默认值), - 或者不应该抛出异常(例如,如果异常来自实现特定的底层层,并且其实现细节不应该对调用者可见——例如,我不想显示我的DAO使用Hibernate来持久化我的实体,因此我在本地捕获所有HibernateExceptions并将它们转换为我的自定义异常类型)。

好的。重新阐述一下,当无法立即得到解决并需要进行某些“高级”交互以决定如何处理时,将会抛出异常。此外,如果异常仅在一组类之间具有“意义”,则应抛出更通用的异常代替它,以通知调用者需要注意。这听起来大致正确吗? - James P.
1
@James,差不多吧 :-) 如果你所说的“交互”是指需要用户干预,那并不总是需要的。而且,与其使用“更通用”,我更喜欢使用“不同”。用户定义的ConnectionException并不比HibernateException更通用。 - Péter Török

12

以下是我使用的方法:

Throws:

  • 当有错误发生时,你希望代码停止运行。
  • 对于那些在未满足特定前提条件时容易出现错误的方法而言,它非常好用。

Try-Catch:

  • 当你希望以不同方式处理不同类型的错误时使用。
  • 如果你想向最终用户提供有意义的错误信息,那么这个方法非常好用。

我知道很多人总是使用Throws,因为它更简洁,但是它的控制力就没有Try-Catch强。


你几乎永远不应该使用异常处理来控制程序。这是一种可怕的做法。 - CheesePls
2
还有Try-catch-rethrow或try-catch-wrap-throw,即使您可以以有意义的方式处理异常,也不意味着您已经完成了工作。该方法应执行其名称指示它将执行的操作,或应引发异常:http://abstractions-r-us.blogspot.com/2010/06/do-your-work-or-throw-exception-example.html。(我的博客的无耻宣传) - jyoungdev
1
这是我已经思考一段时间的问题。我认为如果需要使用用户定义的异常,可以尝试使用try-catch块。但是我必须说,在方法存根中使用throws会更加简洁,而且在以后进行任何调试时需要排序的代码要少得多。如果在方法存根中使用throws,您必须将错误传递到方法链上,我认为,这一点我不确定。使用try-catch,您可以只让try-catch捕获错误,而无需将其传递到方法链中。我对此并不完全确定,但我认为 - Doug Hauf
这通常是这样的,我想。我知道如果你使用try-catch读取文件,然后在另一个类中声明你的类的实例,你不必让方法抛出异常。但我仍然不确定。 - Doug Hauf

9

我的个人经验法则很简单:

  • 我能以一种有意义的方式处理它吗(从评论中添加)?所以将代码放入try/catch中。通过处理它,我指的是能够通知用户/从错误中恢复或在更广泛的意义上理解此异常如何影响我的代码执行。
  • 否则,将其丢弃

注意:此回复现在是社区维基,请随意添加更多信息。


6
我能以有意义的方式处理它吗? - Nivas
2
你决定是否可以在本地处理异常的标准是什么?这个级别是否足以实际处理异常并显示消息,例如? - James P.
抛弃掉它是一个不好的主意。更好的方法是将它传递给调用堆栈。 - Steve Kuo

3
决定在方法中添加try-catch或throws子句取决于“您希望(或必须)如何处理异常”。如何处理异常是一个广泛而且远非微不足道的问题。它涉及特别是决定在哪里处理异常以及在catch块中实现什么操作。事实上,如何处理异常应该是全局设计决策。因此,回答您的问题,没有经验法则。您必须决定在哪里处理异常,这个决定通常非常具体化,与您的领域和应用要求有关。

2

如果抛出异常的方法具有足够的信息来处理它,则应该捕获异常,生成有用的信息,说明发生了什么以及正在处理哪些数据。


你是否总是将错误异常写入日志文件或控制台?昨晚我在思考这个问题,因为在实际程序中,您将无法看到实际的控制台,所以您的错误应该写入文本文件。那么e.printStackTrace()如何打印呢?在调试时它会输出到控制台,但它还可以输出到哪里? - Doug Hauf

1

我会为您简化一下。 当您认为被调用的方法不负责异常(例如,来自调用者方法的无效参数、要搜索的项、未在集合中可用或获取数据列表)时,请使用throws。 当您认为被调用方法中的功能可能导致某些异常时,请使用try catch块(在被调用方法中处理异常)。


1
一个方法只有在可以对对象的状态、传递给该方法的任何参数以及该方法作用于的任何其他对象做出合理保证时,才应该抛出异常。例如,如果一个方法应该从集合中检索调用者期望包含在其中的项目,则如果集合中预期存在的项目不存在,则可能会抛出已检查异常。捕获该异常的调用者应该预期该集合不包含所询问的项。
请注意,虽然 Java 允许已检查的异常通过声明适当类型异常抛出的方法向上传递,但这种用法通常应被视为反模式。例如,假设某个名为 LookAtSky() 的方法被声明为调用 FullMoonException,并且在月亮全盈时预计会抛出它;进一步假设 LookAtSky() 调用了同样声明为 throws FullMoonExceptionExamineJupiter()。如果由 ExamineJupiter() 抛出了一个 FullMoonException,并且如果 LookAtSky() 没有捕获它并处理或将其包装在其他异常类型中,调用 LookAtSky() 的代码将认为异常是由地球上的月亮充满造成的;它不会知道木星的卫星可能是罪魁祸首。

如果调用者可能期望处理的异常(包括基本上所有受检异常),只有当异常对于方法的调用者意义与被调用方法相同时,才应该允许其通过方法向上传播。如果代码调用了声明为抛出某些已检查异常的方法,但是调用者实际上不希望它抛出该异常(例如因为它认为已经预先验证了方法参数),则应该捕获并将受检异常包装在某个未经检查的异常类型中。如果调用者不希望抛出异常,则调用者不能期望它具有任何特定含义。


1

何时使用什么。我搜索了很多关于这个的信息。 没有硬性规定。

“但是作为开发人员,必须在方法的throws子句中包含Checked异常。这对于编译器知道要检查哪些异常是必要的。 按照惯例,不应在throws子句中包含Unchecked异常。
将它们包括在内被认为是一种糟糕的编程实践。编译器将其视为注释,并且不会对其进行检查。”

来源:Kathy Sierra的SCJP 6书籍


0

如果您使用try catch,当异常发生时,剩余的代码仍将被执行。

如果您指示方法抛出异常,则当异常发生时,代码将立即停止执行。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接