Java 异常没有被捕获?

我有一个小的 理论上的问题与 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(经过测试,确实如此)。

我的问题是,例外(“2”)去了哪里?

19016 次浏览

在 finally 块中引发的异常将抑制前面在 try 或 catch 块中引发的异常。

Java7示例: http://ideone.com/0YdeZo

贾瓦多克的为例:


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 块抛出的。


Java7的新 try-with语法增加了异常抑制的另一个步骤: try 块中抛出的异常抑制 try-with part 中早期抛出的异常。

来自同一个例子:

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 块抛出,并且最多可以抛出两个异常 从 try-with-resources 语句中关闭 对象引发异常。如果从 块引发一个或多个异常 语句,然后从 将禁用 try-with-resources 语句,并引发异常 块抛出的那个 可以检索这些被禁止的 控件中调用 Throwable.getSuppress 方法来实现异常 Try 块引发的异常。


在有问题的代码中,每个代码块都明显地丢弃了旧的异常,甚至没有记录它,当您试图解决一些 bug 时,这是不好的:

Http://en.wikipedia.org/wiki/error_hiding

来自 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")将被传播。

因为 throw new Exception("2");是从 catch块抛出的,而不是从 try抛出的,所以不会再次被捕获。
14.20.2. try-finally 和 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
}

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

此外,抛出异常本身不会导致任何输出。

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

说明: 当异常发生时,它抛出的对象由 catch 块处理。但是当 CATCH 块本身发生异常时,它的对象将转移到 OUTERCATCH 块(如果有的话)以进行异常处理。这里也是。带有消息“2”的异常对象被传输到 OUTER catch Block。等等..在离开内部 try-catch 块之前,必须最终执行。这里发生了我们所关心的变化。一个新的 EXCEPTION 对象(带有消息“3”)被抛出,或者这个 finally 块替换了已经抛出的 EXCEPTION 对象(带有消息“2”)。结果,当打印 Exception 对象的消息时,我们得到了重写的值,即“3”而不是“2”。

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

根据你的代码:

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 在 try-catch (或者只有 try,如果没有发生任何异常)语句后执行 # 3并打印 C - # 4并抛出新的异常;
  4. 这个被外部的 catch 语句 # 5捕获;

结果是 ABC3,而 21以同样的方式被省略