170

我对 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") 去哪儿了?

4

6 回答 6

197

来自Java 语言规范 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")被传播。

于 2013-08-07T07:48:10.257 回答
19

finally 块中抛出的异常会抑制之前在 try 或 catch 块中抛出的异常。

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

来自Javadoc 的示例:


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 块抛出的异常被抑制。


新的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 方法抛出。您可以通过从 try 块抛出的异常中调用 Throwable.getSuppressed 方法来检索这些抑制的异常。


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

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

于 2013-08-07T07:47:31.607 回答
9

由于是从块throw new Exception("2");中抛出而不是,它不会被再次捕获。14.20.2。执行 try-finally 和 try-catch-finallycatchtry

这就是发生的事情:

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
}
于 2013-08-07T07:48:25.037 回答
5

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

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

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

于 2013-08-07T13:57:01.440 回答
2

finally块始终运行。您return来自 try 块内部或引发异常。在finally块中抛出的异常将覆盖在 catch 分支中抛出的异常。

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

于 2013-08-07T07:51:08.123 回答
0

根据您的代码:

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 语句和 print 捕获B - # 2
  3. 块最终# 3在 try-catch (或仅尝试,如果没有发生任何异常)语句之后执行并打印C - # 4并抛出新异常;
  4. 这个已经被外部 catch 语句捕获了# 5

结果是ABC3。和2被省略的方式相同1

于 2013-08-13T22:40:24.183 回答