try/catch与throws Exception的区别

129

这些代码语句是否等价?它们之间有什么区别吗?

private void calculateArea() throws Exception {
    ....do something
}

private void calculateArea() {
    try {
        ....do something
    } catch (Exception e) {
        showException(e);
    }
}

3
这不算是一个答案,但你可能会对Ned Batchelder的文章“雨林中的异常”感兴趣,它有助于解释何时应优先选择一种样式而不是另一种样式的一般情况。 - Daniel Pryden
1
在catch中,你是不是想问如果使用"throws e"代替"showException(e)"(或者根本不使用try/catch)? - Entree
10个回答

161
是的,两者存在着巨大的区别-后一个会吞噬异常(虽然会显示它),而前一个则会让异常继续传播。(我假设showException不会重新抛出它。)
因此,如果您调用第一个方法并且“做某事”失败了,则调用者必须处理该异常。如果您调用第二个方法并且“做某事”失败了,则调用者根本看不到异常...这通常是一件坏事,除非showException已经真正地处理了异常,修复了任何问题,并确保calculateArea已经达成其目的。
您可以通过这种方法来判断,因为你不能调用第一个方法,除非你自己捕获了Exception,或者声明您的方法也可能会抛出它。

13
当你提到“除非真正处理了异常”的时候,那是一个很好的观点。我只是想补充一下,捕获“Exception”本身很少会导致对实际异常进行智能的“处理”,这也是人们建议你尽可能捕获最具体异常的原因。 - Bill K
21
+1. 因为Jon Skeet需要更多的声望。哦,而且回答也很好。 - Jonathan Spiller

29

它们的不同在于处理 Exception 的责任所在。第一种仅仅是 throws Exception,因此没有进行处理。调用该方法的代码需要处理这个 Exception。第二种捕获并处理了 Exception,因此调用者不必进行任何异常处理,前提是 showException() 本身不会抛出另一个异常。


简而言之,我应该始终使用第二个。我是对的吗? 第一个实际上是在程序的不同点使用的方法。这就是为什么我决定将指令组合在一起以供进一步使用,但现在我意识到我犯了一个大错误... - carlos
10
不,这两个模式都是必需的。如果你的方法能够处理异常,就使用第二种模式;否则,使用第一种方式通知调用者。 - Andreas Dolk
你使用哪个版本取决于你的需求 - 基本上在处理异常的什么级别上需要。调用者需要相应地编码。如果调用者正在调用第一个版本,并且您将方法定义替换为第二个版本,则您的调用者代码将被强制处理异常,因为这是已检查异常。 - samitgaur

19

是的。声明 throws Exception 的版本将要求调用代码处理异常,而显式处理异常的版本则不需要。

即:

performCalculation();

与将处理异常的负担移交给调用方相比:

try {
    performCalculation();
catch (Exception e) {
    // handle exception
}

9
是的,它们之间有很大的区别。在第一个代码块中,你将异常传递给调用代码。而在第二个代码块中,你自己处理异常。哪种方法是正确的完全取决于你正在做什么。在某些情况下,你希望你的代码处理异常(例如,如果找不到文件并且你想创建它),但在其他情况下,你希望调用代码处理异常(找不到文件并且他们需要指定一个新文件或创建它)。
另外,通常来说,你不应该捕获一个通用异常。相反,你会想要捕获特定的异常,如 FileNotFoundExceptionIOException ,因为它们可能意味着不同的事情。

3

有一种情况我们不能使用throws,必须使用try-catch。有一个规则是“重写的方法不能抛出比其父类更多的异常”。如果有额外的异常,应该使用try-catch进行处理。考虑以下代码段,有一个简单的基础类。

package trycatchvsthrows;

public class Base {
    public void show()
    {
        System.out.println("hello from base");
    }
}

还有它的派生类:

package trycatchvsthrows;

public class Derived extends Base {

    @Override
    public void show()   {
        // TODO Auto-generated method stub
        super.show();

        Thread thread= new Thread();
        thread.start();
        try {
            thread.sleep(100);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        // thread.sleep(10);
        // here we can not use public void show() throws InterruptedException 
        // not allowed
    }
}

当我们需要调用 thread.sleep() 方法时,我们被迫使用 try-catch 块,这里不能使用:

 public void show() throws InterruptedException

由于重写的方法不能抛出额外的异常。


我相信并不是每个人都意识到这个警告。指出得很好。 - ivanleoncz

2
如果您抛出了一个异常,子方法(覆盖此方法)应该处理这个异常。
例如:
class A{
public void myMethod() throws Exception{
 //do something
}
}

A a=new A();
try{
a.myMethod();
}catch Exception(e){
//handle the exception
}

1

我假设你所说的“相同”,是指行为上的相同。

函数的行为可以通过以下方式确定:

1)返回值

2)抛出异常

3)副作用(例如堆、文件系统等的更改)

在这种情况下,第一种方法传播任何异常,而第二种方法不会抛出已检查的异常,并且也会吞噬大部分未经检查的异常,因此行为是不同的。

但是,如果您保证“做某事”永远不会引发异常,则行为将是相同的(尽管编译器将要求调用者处理异常,在第一个版本中)。

--编辑--

从API设计的角度来看,这两种方法在其契约方面完全不同。此外,不建议抛出类异常。尝试抛出更具体的内容以使调用者更好地处理异常。


0
调用此方法的调用者需要捕获此异常或在其方法签名中声明重新抛出。
private void calculateArea() throws Exception {
    // Do something
}

在下面的 try-catch 块示例中,调用此方法的调用方不必担心处理异常,因为已经处理过了。
private void calculateArea() {
    try {
        // Do something

    } catch (Exception e) {
        showException(e);
    }
}

0
private void calculateArea() throws Exception {
    ....do something
}

这会抛出异常,因此调用者负责处理该异常,但如果调用者不处理异常,则可能会将其传递给 JVM,从而导致程序异常终止。

而在第二种情况下:

private void calculateArea() {
    try {
        ....do something
    } catch (Exception e) {
        showException(e);
    }
}

在这里,异常由被调用者处理,因此程序不会异常终止。

Try-catch 是推荐的方法。

在我看来,

  • Throws 关键字主要与已检查异常一起使用,以说服编译器,但它不能保证程序正常终止。

  • Throws 关键字将异常处理的责任委托给调用者(JVM 或另一个方法)。

  • Throws 关键字仅适用于已检查的异常,对于未经检查的异常,没有使用 throws 关键字的必要。


0
很多时候,你希望调用者处理异常。假设你让调用者调用一个方法,该方法又调用另一个方法,再调用另一个方法,而不是让每个方法都处理异常,你可以在调用者处处理它。除非你想在某个方法失败时执行某些操作。

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