约书亚·布洛赫在《有效的Java》中说过

为以下情况使用检查异常 可恢复条件和运行时 编程错误的例外 (第二版第58项)

看看我理解的对不对。

以下是我对受控异常的理解:

try{
    String userInput = //read in user input
    Long id = Long.parseLong(userInput);
}catch(NumberFormatException e){
    id = 0; //recover the situation by setting the id to 0
}

1. 上述异常是否被认为是受控异常?

2. RuntimeException是未检查的异常吗?

以下是我对未检查异常的理解:

try{
    File file = new File("my/file/path");
    FileInputStream fis = new FileInputStream(file);   
}catch(FileNotFoundException e){

//3. What should I do here?
    //Should I "throw new FileNotFoundException("File not found");"?
    //Should I log?
    //Or should I System.exit(0);?
}

4. 现在,上面的代码不能也是一个受控异常吗?我可以试着挽回这样的局面吗?我可以吗?(注:我的第三个问题在上面的陷阱里)

try{
    String filePath = //read in from user input file path
    File file = new File(filePath);
    FileInputStream fis = new FileInputStream(file);   
}catch(FileNotFoundException e){
    //Kindly prompt the user an error message
    //Somehow ask the user to re-enter the file path.
}

5. 人们为什么要这样做?

public void someMethod throws Exception{

}

为什么他们让异常冒出来?早点处理错误不是更好吗?为什么要冒出来?

6. 我是否应该冒泡出确切的异常或使用异常掩盖它?

以下是我的阅读资料

在Java中,什么时候应该创建检查异常,什么时候应该是运行时异常?

何时选择已检查异常和未检查异常


当前回答

某个异常是否为“受控异常”与是否捕获它或在捕获块中做了什么无关。它是异常类的属性。Exception的子类,除了RuntimeException及其子类,都是受控异常。

Java编译器迫使您要么捕获已检查的异常,要么在方法签名中声明它们。它本应提高程序的安全性,但大多数人的意见似乎是,它不值得它带来的设计问题。

为什么他们让异常冒泡 起来吗?不是处理错误越快 更好吗?为什么要冒出来?

因为这就是例外的意义所在。如果没有这种可能性,就不需要异常。它们使您能够在您选择的级别上处理错误,而不是强迫您在错误最初发生的低级方法中处理它们。

其他回答

我认为受控异常对于使用外部库的开发人员是一个很好的提醒,在异常情况下,该库中的代码可能会出错。

所有异常都必须是检查异常。

Unchecked exceptions are unrestricted gotos. And unrestricted gotos are considered a bad thing. Unchecked exceptions break encapsulation. To process them correctly, all the functions in the call tree between the thrower and the catcher must be known to avoid bugs. Exceptions are errors in the function that throws them but not errors in the function that processes them. The purpose of exceptions is to give the program a second chance by deferring the decision of whether it's an error or not to another context. It's only in the other context can the correct decision be made.

关于未检查异常和已检查异常之间的区别,我最喜欢的描述来自Java教程的试用文章“未检查异常-争议”(很抱歉在这篇文章中介绍了所有基本的内容-但是,嘿,基本的有时是最好的):

这是底线原则:如果客户可以合理地 希望从异常中恢复,使其成为受控异常。如果 客户端不能做任何事情来从异常中恢复 未经检查的异常

“抛出哪种类型的异常”的核心是语义的(在某种程度上),上面的引用提供了一个很好的指导方针(因此,我仍然被c#摆脱受控异常的概念所震撼——特别是Liskov认为它们有用)。

接下来的问题就变得合乎逻辑了:编译器希望我显式地响应哪些异常?你希望客户能从中恢复过来。

为了回答最后一个问题(上面的其他问题似乎都得到了彻底的回答),“我应该冒泡出确切的异常还是使用exception掩盖它?”

我猜你的意思是这样的:

public void myMethod() throws Exception {
    // ... something that throws FileNotFoundException ...
}

不,总是声明尽可能精确的异常,或者一个这样的列表。你声明你的方法能够抛出的异常是你的方法和调用者之间契约的一部分。抛出"FileNotFoundException"意味着可能文件名无效,无法找到该文件;调用者需要聪明地处理这个问题。抛出异常意味着“嘿,该死的事情发生了。交易。”这是一个非常糟糕的API。

在第一篇文章的注释中有一些例子,其中“throws Exception”是一个有效且合理的声明,但对于您所编写的大多数“正常”代码来说,情况并非如此。

我只是想添加一些根本不使用受控异常的理由。这不是一个完整的答案,但我觉得它确实回答了你的部分问题,并补充了许多其他的答案。

Whenever checked exceptions are involved, there's a throws CheckedException somewhere in a method signature (CheckedException could be any checked exception). A signature does NOT throw an Exception, throwing Exceptions is an aspect of implementation. Interfaces, method signatures, parent classes, all these things should NOT depend on their implementations. The usage of checked Exceptions here (actually the fact that you have to declare the throws in the method signature) is binding your higher-level interfaces with your implementations of these interfaces.

让我给你们看一个例子。

让我们有一个像这样漂亮干净的界面

public interface IFoo {
    public void foo();
}

现在我们可以编写方法foo()的许多实现,就像这样

public class Foo implements IFoo {
    @Override
    public void foo() {
        System.out.println("I don't throw and exception");
    }
}

类Foo完全没问题。现在让我们第一次尝试Bar类

public class Bar implements IFoo {
    @Override
    public void foo() {
        //I'm using InterruptedExcepton because you probably heard about it somewhere. It's a checked exception. Any checked exception will work the same.
        throw new InterruptedException();
    }
}

这个类Bar不能编译。由于InterruptedException是一个已检查异常,您必须捕获它(在方法foo()中使用try-catch)或声明您正在抛出它(在方法签名中添加抛出InterruptedException)。因为我不想在这里捕获这个异常(我希望它向上传播,这样我就可以在其他地方正确地处理它),让我们改变签名。

public class Bar implements IFoo {
    @Override
    public void foo() throws InterruptedException {
        throw new InterruptedException();
    }
}

这个类Bar也不能编译!Bar的方法foo()不会覆盖IFoo的方法foo(),因为它们的签名不同。我可以删除@Override注释,但我想编程接口IFoo像IFoo foo;然后再决定使用哪个实现,比如foo = new Bar();如果Bar的方法foo()没有覆盖IFoo的方法foo,当我执行foo.foo();它不会调用Bar的foo()实现。

To make Bar's public void foo() throws InterruptedException override IFoo's public void foo() I MUST add throws InterruptedException to IFoo's method signature. This, however, will cause problems with my Foo class, since it's foo() method's signature differs from IFoo's method signature. Furthermore, if I added throws InterruptedException to Foo's method foo() I would get another error stating that Foo's method foo() declares that it throws an InterruptedException yet it never throws an InterruptedException.

正如您所看到的(如果我在解释这些东西方面做得不错的话),抛出InterruptedException这样的检查异常的事实迫使我将我的接口IFoo绑定到它的一个实现上,这反过来又会对IFoo的其他实现造成严重破坏!

这就是受控异常很糟糕的一个重要原因。在帽。

一种解决方案是捕获已检查异常,将其包装在未检查的异常中,然后抛出未检查的异常。