何时应选择已检查异常/未检查异常?

4

我从各种教程中学到:“如果客户端可以合理地预期从异常中恢复,那么将其设置为已检查的异常。如果客户端无法从异常中恢复,则将其设置为未检查的异常。”

我真的想通过一些代码示例来看到前面语句的有效性。 例如:

try {
        br.readLine();
    } catch (IOException e) {

        e.printStackTrace();
    }

在这里,IOException是一个受检异常。那么当出现这个异常时,我应该如何恢复呢?在这里,我排除了异常记录和异常重新抛出任务,因为它们实际上并没有恢复即使事情变得正确。那么,应该对此进行哪些修改以便从中恢复过来呢?

如果有一种方法可以从中恢复,那么同样的方法也可以应用于以下代码:

 try{
    Integer.parseInt("ghg4");
 }catch(NumberFormatException nfe){   
   }

在这里,NumberFormatException是一个运行时/未检查的异常。那么,如果有一种方法可以从中恢复,为什么它首先被声明为运行时异常呢?


1
可能是何时选择已检查和未检查的异常的重复问题。 - Yahia
+1 那个链接。我发现 OscarRyz 的评论特别有用。 - Guillaume Polet
3个回答

1
C#摒弃了被检查的异常,我认为这是一个好主意。
在我编写的代码中,我大多遵循了C#的模式,将所有异常都扩展自RuntimeException。然而,有一些地方我使用了被检查的异常,以强制自己和使用我的代码的人对更常见的异常情况作出响应。

认为这不是一个好的、有建设性的答案?请说明原因。 - ControlAltDel
抛弃已检查异常绝对不是一个好主意。通常它会隐藏有趣的异常,而保持程序稳定的唯一解决方案就是在所有调用的顶部放置一个大的try/catch块,导致无趣的反馈。 - Guillaume Polet
不同意,但既然我已经“错了”,那我想我还是错了。 - ControlAltDel
@Guillaume:如果检查异常是一个好主意,那么我期望其他语言现在已经借鉴了这个想法,但我想不出有哪些语言这样做了。有很多带有检查异常的程序在异常处理方面存在严重问题,我认为对于检查异常的有效性存在合理的怀疑空间。 - Nathan Hughes
@Nathan,然而Java开发者们仍然在使用它们。所有这些人都有什么问题吗?;-) - Guillaume Polet

1

RuntimeException和Exception之间没有明确的边界。一般来说,过度使用Exception的后代会导致catch子句的链(例如反射处理代码),或者只是对catch (Exception e)不关心特定类型。有许多不同的实践,这个问题是有争议的 - 也就是说,它并不简单,没有唯一正确的方法来设计应用程序中的异常。


我坚持以下规则:
  1. 如果异常将被单独处理并且可区分于简单的输入数据错误或类似情况,则为已检查异常。
  2. 如果异常是由明显错误的输入条件或代码中的错误(如NPE)引起的,则为运行时异常。
从这个逻辑出发,例如我会让IOException成为RuntimeException的子类。

更新:关于 IOException 并非是非黑即白的。但一些 IOE 的后代(例如 FileNotFoundException,MalformedURLException 等)- 明显只是错误的输入。另外,当您使用 ByteArray IO 流(或类似流)来处理从未发生的 IOE 时,这会让事情变得很烦人。


+1,但为什么IOException应该扩展RuntimeException?良好的输入检查无法防止这种情况;I/O操作本质上是有风险的。 - Taymon
同意。这并不是非黑即白的问题。但是,一些IOE后代(如FileNotFoundException、MalformedURLException等)绝对只是错误的输入。而且当你使用ByteArray IO Streams(或类似的流)来处理从未发生过的IOE时,会让人感到烦恼。 - Eugene Retunsky

1

我看到三种异常。在一个极端是那些你无法处理的异常,比如NullPointerException。你要么在代码中非常高层次地处理它,要么根本不处理它。检查它是荒谬的。

在另一端是提供有意义信息的异常。它们有点像返回值,有时是复杂的返回值,当方法已经有返回值时。它们也是跳转调用堆栈的简单方式。(我可以写一本关于这个的书,但我会在这里停止。)EOFException应该是一个很好的例子。文件有它们的结尾,你迟早会遇到它,你不想每次读取时都去检查它。在这种情况下,需要使用已检查异常。(我认为user1291492会同意我的观点。)它可能发生,任何调用读取方法的人都应该为此做好准备。他们会更喜欢编译器错误而不是运行时错误。

现在,对于这种类型的异常,您不希望添加堆栈跟踪!这需要大量时间。调用者只需要知道他遇到了EOF,而不是它发生在IO系统的深处的哪个位置!此外,除非有有趣的信息要返回,否则该异常本身应该是一个final static引用,生成一次并用于每个发生的EOF。
第三种类型是中间的那些,它们是Java库使用的,比如真正的EOFException。它们毫无意义。要么调用者期望永远不会收到EOF(例如,他自己放置了标记),并且EOFException与NullPointerException具有相同的性质,要么他期望它并且不需要堆栈跟踪的麻烦和处理时间的损失。我认为问题在于Java设计师本身——我必须承认自己也有这个问题——很少考虑这个问题,他们不确定这些异常可能属于前两类中的哪一类。即使在同一个程序中,在一个地方,EOFException可能表示程序的完全失败。在另一个地方,它可能是发现文件已被读取的正常方式。因此,最终结果是大量异常都执行这两项工作,并且执行得很差,强制程序员在无法做任何事情时使用try、catch和throws,并交给他们不需要的复杂堆栈跟踪。

补充:我应该明确指出,你可以通过接受已经读完文件并继续处理的方式来恢复真正的EOF异常,可能需要在catch块中使用break语句。然而,还有其他正确的方法来捕获EOF,所以EOFException通常意味着一个真正的问题,比如空指针异常。奇怪的是,我使用NumberFormatException的方式,我认为它应该被检查。当我想要一个数字时得到"AAA"是非常常见的用户错误,而不是编程错误。


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