调用堆栈:catch和throws的区别

3
什么时候使用 "catch",什么时候使用 "throws"?
try {
    //stuff
} 
catch (MyException me) {
    //stuff
}

对比

public void doSomething() throws MyException {
    //stuff
}

在“throws”情况下,我应该在调用堆栈的哪个位置放置我的catch?
Main
    ----- Function 1
        ----- Function 2
            ----- Function 3 (generate exception)

如果我从函数3传播异常到函数2,为什么函数2不应该做同样的事情?这样最终我将在"main"中处理所有异常,我认为把所有代码都放在try块中并不是一个好习惯,对吗?
那么,在“catch”和“throws”之间选择的逻辑方式是什么?在第二种情况下,我应该在调用堆栈中放置我的catch吗?

8
只有当你知道如何处理异常时,才应该捕获它。 - SLaks
我的捕获规则:只有在以下情况下才要捕获异常:1)存在一些合理的操作需要执行;或者2)日志记录并重新抛出异常。不幸的是,Java 还要求我添加第 3 条规则:将已检查异常包装成未检查异常,在通过 throws 传播该已检查异常过程中过于繁琐。 - user2246674
当你想要捕获异常时,请使用catch,而当你不想要捕获异常时,请使用throws。当然,你想要做什么在很大程度上取决于你与用户的“契约”。 - Hot Licks
3个回答

2
它们基本上是相反的。throws 表示一个函数允许抛出异常;catch 表示一个块(即 try 块)预期可能会有异常抛出,并准备好处理它。
用棒球的比喻来说,投手会 throws 扔一个棒球给接手方去接。接手方 catches 接到棒球并以某种方式处理它。(也许比喻不太准确,因为接手方通常是通过将棒球扔回给投手来处理它。:) )这里,投手是一个方法,而接手方是一个 try-catch-[finally] 块。

如果你想扩展棒球隐喻,每次你想把球扔回投手手中,你都会得到一个新的投手。 - Jason Sperske
@JasonSperske 现在我们开始哲学了!http://quotationsbook.com/quote/5712/#sthash.foUKSBpx.dpbs - yshavit
另外,您可能希望添加一些关于已检查异常与未检查异常的解释。已检查异常就像棒球比赛中的投球,而未检查异常则像被击出公园的飞球打中头部。 - Jason Sperske

0

每当方法的调用者需要捕获或传递它时,您应该声明一个方法抛出已检查异常。每当您准备好立即处理异常时,您应该捕获异常。

例如,如果您正在编写具有图形界面并从文件中读取的核心的程序,则核心类无法告诉用户发生了错误,这是图形界面的工作。因此,在核心程序中,getSomethingFromFile() 等方法可能会抛出 IOException。如果图形界面调用 getSomethingFromFile() 并确定存在读取错误,则图形界面可以显示对话框以通知用户,因此它已经准备好立即捕获异常。在这种情况下,getSomethingFromFile() 调用应该被包含在 try/catch 中。


0
如果您采用“throw-catch”方法来处理错误,那么实际的错误处理必须由具有相应责任的组件完成,特别是因为这样可以将逻辑封装在其所属的位置。
异常被某个知道如何处理它以及必须采取的行动的类捕获。在某些特殊情况下,您可以通过将其包装在另一个异常中(将异常设置为其原因)来重新抛出异常。以ORM为例,任何低级别异常都会被包装在例如“PersistenceException”的异常中,该异常可以将SQLSyntaxException作为其原因。
如果您没有适当的工具来在某个上下文中管理异常并且希望将其传播到可以正确管理的更高层/层,则会使用“throws”。
让我举个“大局观”的例子:
  1. 保存实体到数据库
  2. 通信错误
  3. 发生异常,因此您的持久化对象必须处理它。
  4. 捕获它,包装它并将其重新抛出为自己的异常之一(我反对让持久化异常传播到更高层...但这只是我的想法)。
  5. 持久化抛出一个被模型捕获的异常。
  6. 模型重试操作。
  7. 另一个失败(相同的包装+抛出)。
  8. 模型通知失败并提升报告到视图。这就是我所说的“被知道该怎么做的人捕获”的意思。
  9. 视图向用户显示“今天无法保存,抱歉”。

示例包含throwcatch情况,希望能够澄清。


你不喜欢让持久性异常传播到更高层,这并不是独一无二的。除非API记录了调用者应该处理的每个异常,否则调用者可能被迫使用Pokemon异常处理。声明例程应指定它们期望抛出的异常的想法实际上是一个好主意;Java实现的弱点在于明显假设如果任何调用者想要处理某个条件,则所有调用者都将想要处理它。如果...检查异常是一件好事。 - supercat
可以简洁地指定方法调用不应该抛出任何已检查的异常,除非明确捕获了这些异常,并且由此引发的任何已检查的异常都应该被包装并重新抛出为其他异常类型。请注意,方法本身抛出SomeCheckedException并不意味着在嵌套调用中抛出的SomeCheckedException应该被视为“预期”的并传播上来。如果调用方法的代码没有预期到异常,则应将其包装在未经检查的异常类型中。 - supercat
@supercat 你会惊讶于我曾经参与的一些讨论。像“无论在哪一层,持久化异常都是持久化异常”这样的争论对我来说似乎很荒谬。我的回答通常包含讽刺:“哦,你是对的!让我在我的JSF托管Bean中导入Hibernate的异常层次结构吧”……我同意你关于使用未检查异常的观点。我总是定义自己的异常层次结构,并尽可能少地保留已检查的异常。我的例子很简单,但已检查与未检查是一个完整的话题。 - Fritz
我熟悉的异常处理框架(主要是Java和.net)最大的缺陷在于假设想要处理异常的代码将期望解决它,更糟糕的是,异常类型通常足以回答这两个问题。我认为,在许多情况下,正确的模式应该是当某些数据结构的工作出现意外异常时,明确地使任何可能被损坏的内容无效。如果永远不需要使用可能已经损坏的任何内容,则代码会快乐地运行。 - supercat
相比之下,如果其中一个被损坏的东西对于程序的正常运行至关重要,那么它被使无效的事实将会导致程序崩溃。如果代码始终遵循这样的模式:在异常传递到调用堆栈之前,任何可能被意外异常破坏的内容都会被明确地使无效,那么 Pokemon 异常处理就是安全的。不幸的是,这种模式并不普遍遵循,因此通常没有明智的方法来处理意外异常。 - supercat
@supercat 一个非常有趣的观点,多样性是保持代码(和编码)润滑的关键。不幸的是,当“某些东西起作用”时,人们往往倾向于“保持原样”... 尽管如此,在即将推出的Java版本中,异常处理可能需要进行一次很好的改进。 - Fritz

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