【问题标题】:Exceptions propagate up Call Stack异常向上传播调用堆栈
【发布时间】:2013-03-26 01:46:32
【问题描述】:

我很难完全理解让异常在调用堆栈中传播的概念或真正的用处。我知道如何创建它们,但我真的不知道它们什么时候会被使用,比如在一个简单的现实世界数学应用程序中。

public void method1(){
    try{
        method2();
    }
    catch(Exception e){
        e.printStackTrace();
    }
}

public void method2(){
      try{
          throw new Exception();
      }
      finally{
         System.out.println("no exception, try cleanup");
      }
}

我知道这基本上就是它的工作方式,虽然它可能会更多地涉及更多的异常和函数,但我并没有真正理解使用这些而不是在每个函数中都有捕获的意义。

【问题讨论】:

  • 因为每个函数中都有一个捕获隐藏异常。这是一个常见的“ick code”错误。如果您不打算处理异常,不要捕获它。如果您只想记录它,而不进行其他处理,也重新抛出异常。

标签: java exception try-catch


【解决方案1】:

...但我真的不明白使用这些而不是在每个函数中都有捕获。

关键是调用堆栈中的下一个函数可能不知道如何处理异常。示例:

public class Test {

    public Object doSomething(String source) throws IOException {
        try (InputStream is = openAsStream(source)) {
            // ... read and process stuff
            return ...
        }
    }

    public InputStream openAsStream(String name) throws IOException {
        String fileName = // ... do something with 'name'
        return new FileInputStream(name);
    }

    public static void main(String[] args) {
        // ...
        Test t = new Test();
        try {
            t.doSomething(args[0]);
        } catch (IOException ex) {
            System.err.println("Cannot handle '" + args[0] + "'");
        }
    }
}

openAsStream 调用 FileInputStream 构造函数,该构造函数可能会抛出 IOException。 openAsStream 方法无法从中恢复,因此它让它传播。 doSomething 方法也不知道如何处理它,所以它允许它传播。最后,异常到达main ...它知道如何向用户解释问题。


现在您可以编写openAsStream 来捕获IOException,打印错误消息并返回null。但这将是一个很大的错误:

  • openAsStream() 不(并且不应该)知道是否/如何向用户报告问题。

  • 如果它返回一个null给调用者,那么调用者必须测试看看调用的结果是否是null ...并采取替代行动。

    李>

关键是方法应该只处理可以在该级别充分处理的异常。应该允许其他人传播。 (或者可能包含在另一个异常中......如果这是 API 设计所要求的。)

【讨论】:

    【解决方案2】:

    默认情况下,异常传播使您的代码在错误时快速失败。

    考虑异常传播的替代方法 - 返回错误代码。如果您的代码的调用者意外或故意不测试错误代码,那么他们可能会使用您的方法,而不知道您的对象现在处于不可用状态,并继续调用方法并导致未定义的行为/内存损坏。如果您改为抛出异常,那么如果调用者忘记捕获异常,那么它们不会做可怕的事情,而是会快速失败,并且可以提醒程序员它被抛出的位置、原因以及如何处理它。例外是响亮而令人讨厌的,因为它们表明需要考虑的条件。

    【讨论】:

      【解决方案3】:

      有时生成异常的代码不知道如何正确处理它。如果您在一段事务代码中,并且发生了故障,则该方法/组件可能无法做更多的事情,而只是在尝试处理异常时记录该异常。另一方面,在一层或多层上,您可以尝试重新建立连接,或向请求者提供详细的错误响应。

      【讨论】:

      • 我想我觉得奇怪的部分是发生异常的函数无法处理异常。
      • @sl133 想象一下有一个异常,因为一个函数——比如说,DbConnection connect(String dbConnStr)——试图连接到一个数据库,但是连接被拒绝了。这个函数通常返回打开的数据库连接——遇到这种“异常情况”应该怎么办?
      • 它必须以某种方式提醒连接被拒绝,所以我猜你会在这里抛出一个异常,然后父函数会处理发生异常时发生的事情。
      • @sl133 是的 - 让调用者/父母处理它。或者,如果它不想/不能继续向上传播异常。
      【解决方案4】:

      调用者通常有适当的上下文来处理问题,而执行生成异常的操作的代码却没有。假设我是一个高级程序,想要将一些数据写入文件。我正在调用的低级服务,我们称之为 writeFile() 可以出于多种原因抛出 IOException。

      编写 writeFile() 的人将不知道将使用 writeFile() 的上下文。如果 writeFile() 失败,它是否应该尝试重新写入文件?它应该尝试多少次?它应该放弃吗?因为在完成某些任务的方案中编写低级函数 writeFile() 的上下文的程序员已经陷入困境,程序员无法预料调用者会如何处理错误情况。

      而不是试图猜测调用者希望如何处理错误(一个不可能的任务),writeFile() 的程序员向调用 writeFile() 的客户端指示有一些“未解决的问题”需要出现问题时回答。每个问题都由一个异常类指示,当客户端程序员捕获该异常时,客户端程序员正在使用客户端程序员永远不希望拥有的上下文来编写该开放问题的答案。

      简而言之,如果您曾经看到一个方法列出了已检查的异常,那么编写该异常的程序员会说“调用此方法的人将有适当的上下文来决定如何处理该异常情况。我愿意不是。”

      【讨论】:

      • 那么 writeFile() 会捕获 IOException,然后再次抛出它吗?或者如果 writeFile() 有一个 try catch,父级将如何捕获该异常?
      • 在 Java 中,每个方法都声明它可以抛出什么类型的 Checked 异常。例如 writeFile() 的程序员会写他的方法声明 void writeFile() throws IOException { CODE HERE ...} 然后在 CODE HERE 部分的某个地方 writeFile() 的程序员会检查一些条件 IF(FOO== BAR),如果不是真的抛出异常 {throw new IOException();}
      • 因为 writeFile() 级别非常低,它实际上会创建异常,然后将其抛给调用客户端处理。这是设计使然。这篇文案其实还不错。 docs.oracle.com/javase/tutorial/essential/exceptions/index.html
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2010-11-28
      • 1970-01-01
      • 2011-02-06
      • 2013-03-31
      • 1970-01-01
      • 2012-10-15
      • 2017-07-26
      相关资源
      最近更新 更多