【问题标题】:Java exception not caught?Java 异常没有被捕获?
【发布时间】:2013-08-08 11:33:57
【问题描述】:

我有一个关于 try-catch 结构的理论上小问题。

我昨天参加了关于 Java 的实践考试,但我不明白以下示例:

try {
    try {
        System.out.print("A");
        throw new Exception("1");
    } catch (Exception e) {
        System.out.print("B");
        throw new Exception("2");
    } finally {
        System.out.print("C");
        throw new Exception("3");
    }
} catch (Exception e) {
    System.out.print(e.getMessage());
}

问题是“输出会是什么样子?”

我很确定它会是 AB2C3,但令人惊讶的是,这不是真的。

正确答案是 ABC3(经过测试,确实是这样)。

我的问题是,Exception("2") 去哪儿了?

【问题讨论】:

  • +1 啊,伙计,我知道这个答案。我在一次采访中被问到这个问题。这是一个很好的问题,有助于理解 try/catch/finally 在堆栈上的工作方式。
  • 只有一个打印语句可以打印一个数字(最后一个:print(e.getMessage()))。你以为输出会是AB2C3:你以为最外面的catch块会被执行两次吗?
  • 在java中,在执行将控制权转移出catch块的指令之前,如果finally块存在,就会执行它。如果只有 finally 块中的代码没有将控制权转移到外部,则执行 catch 块中的延迟指令。

标签: java exception try-catch


【解决方案1】:

finally 块中抛出的异常会抑制 try 或 catch 块中早先抛出的异常。

Java 7 示例:http://ideone.com/0YdeZo

来自Javadoc's 示例:


static String readFirstLineFromFileWithFinallyBlock(String path)
                                                     throws IOException {
    BufferedReader br = new BufferedReader(new FileReader(path));
    try {
        return br.readLine();
    } finally {
        if (br != null) br.close();
    }
}

然而,在这个例子中,如果方法 readLine 和 close 都抛出 异常,则方法 readFirstLineFromFileWithFinallyBlock 抛出 finally 块中抛出的异常;例外 从 try 块抛出被抑制。


Java 7 新的try-with 语法增加了异常抑制的另一个步骤:try 块中抛出的异常抑制了之前在 try-with 部分中抛出的异常。

来自同一个例子:

try (
        java.util.zip.ZipFile zf = new java.util.zip.ZipFile(zipFileName);
        java.io.BufferedWriter writer = java.nio.file.Files.newBufferedWriter(outputFilePath, charset)
    ) {
        for (java.util.Enumeration entries = zf.entries(); entries.hasMoreElements();) {
            String newLine = System.getProperty("line.separator");
            String zipEntryName = ((java.util.zip.ZipEntry)entries.nextElement()).getName() + newLine;
            writer.write(zipEntryName, 0, zipEntryName.length());
        }
    }

可以从与 try-with-resources 语句。在上面的例子中,异常可以 从 try 块中抛出,最多可以抛出两个异常 从尝试关闭资源时的 try-with-resources 语句 ZipFile 和 BufferedWriter 对象。如果从 try 块并从 try-with-resources 语句,然后从 try-with-resources 语句被抑制,并抛出异常 由块是由抛出的 writeToFileZipFileContents 方法。您可以检索这些抑制 通过调用 Throwable.getSuppressed 方法从 try 块抛出的异常。


在来自问题的代码中,每个块都明显丢弃旧异常,甚至没有记录它,当您尝试解决一些错误时不好:

http://en.wikipedia.org/wiki/Error_hiding

【讨论】:

    【解决方案2】:

    来自Java Language Specification 14.20.2.

    如果 catch 块由于原因 R 突然完成,则执行 finally 块。然后有一个选择:

    • 如果 finally 块正常完成,则 try 语句由于原因 R 突然完成。

    • 如果 finally 块由于原因 S 突然完成,则 try 语句由于原因 S 突然完成(并且原因 R 被丢弃)

    所以,当有一个catch块抛出异常时:

    try {
        // ...
    } catch (Exception e) {
        throw new Exception("2");
    }
    

    但也有一个 finally 块也会抛出异常:

    } finally {
        throw new Exception("3");
    }
    

    Exception("2") 将被丢弃,仅Exception("3") 将被传播。

    【讨论】:

    • 这甚至适用于return 语句。如果您的 finally 块有返回,它将覆盖 trycatch 块中的任何返回。由于这些“特性”,一个好的做法是 finally 块应该从不抛出异常或有返回语句。
    • 这也是Java 7中try-with-resources继承的优势。如果在关闭资源时产生二次异常,它会保留初始异常,通常使调试更容易。
    【解决方案3】:

    由于throw new Exception("2"); 是从catch 块而不是try 抛出的,因此不会再次被捕获。
    参见14.20.2. Execution of try-finally and try-catch-finally

    这就是发生的事情:

    try {
        try {
            System.out.print("A");         //Prints A
            throw new Exception("1");   
        } catch (Exception e) { 
            System.out.print("B");         //Caught from inner try, prints B
            throw new Exception("2");   
        } finally {
            System.out.print("C");         //Prints C (finally is always executed)
            throw new Exception("3");  
        }
    } catch (Exception e) {
        System.out.print(e.getMessage());  //Prints 3 since see (very detailed) link
    }
    

    【讨论】:

    • 是的,没错,我看到这种情况正在发生,但我正在寻找解释——为什么它会这样
    【解决方案4】:

    finally 块始终运行。你要么在 try 块内return,要么抛出异常。 finally 块中抛出的异常将覆盖在 catch 分支中抛出的异常。

    此外,抛出异常本身不会导致任何输出。 throw new Exception("2"); 行不会写出任何内容。

    【讨论】:

    • 是的,我知道自己不会抛出异常输出,但我没有看到为什么应该删除异常 2 的原因。我又聪明了一点:-)
    • 总是很长一段时间,在很长一段时间内任何事情都可能发生(检查拼图wouter.coekaerts.be/2012/puzzle-dreams
    【解决方案5】:

    你的问题很明显,答案也很简单。。 消息为“2”的异常对象被消息为“3”的异常对象覆盖。

    解释: 当一个Exception发生时,它抛出的对象被catch块处理。但是当 catch 块本身发生异常时,其对象被转移到 OUTER CATCH 块(如果有)进行异常处理。同样发生在这里。带有消息“2”的异常对象被传输到 OUTER catch Block 。 但请稍等.. 在离开内部 try-catch 块之前,它必须最终执行。这里发生了我们关注的变化。抛出一个新的异常对象(带有消息“3”)或者这个 finally 块替换了已经抛出的异常对象(带有消息“2”)。结果,当打印异常对象的消息时,我们得到被覆盖的值,即“3”而不是“2”。

    记住:在 CATCH 块上只能处理一个异常对象。

    【讨论】:

      【解决方案6】:

      根据你的代码:

      try {
          try {
              System.out.print("A");
              throw new Exception("1");   // 1
          } catch (Exception e) {
              System.out.print("B");      // 2
              throw new Exception("2");
          } finally {                     // 3
              System.out.print("C");      // 4 
              throw new Exception("3");
          }
      } catch (Exception e) {             // 5
          System.out.print(e.getMessage());
      }
      

      如您所见:

      1. 打印A并抛出异常# 1
      2. 这个异常被catch语句捕获并打印B - # 2
      3. block finally # 3 在 try-catch(或仅尝试,如果没有发生任何异常)语句之后执行并打印 C - # 4 并抛出新异常;
      4. 这个已经被外部catch语句# 5捕获;

      结果是ABC3。并且21一样被省略

      【讨论】:

      • 对不起,Exception("1") 没有省略,但是被成功捕获了
      • @Black Maggie 它被缓存并抛出了新异常 => 这没有被缓存并且程序终止。在这个块最终被执行之前。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-11-04
      • 2019-08-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多