为啥以相反的执行顺序处理 try-with-resource 的抑制异常?

Posted

技术标签:

【中文标题】为啥以相反的执行顺序处理 try-with-resource 的抑制异常?【英文标题】:Why suppressed exception of try-with-resource is handled in a reverse order of execution?为什么以相反的执行顺序处理 try-with-resource 的抑制异常? 【发布时间】:2015-07-17 18:09:46 【问题描述】:

在正常的try-catch-finally中,像这样,

try 
    throw new ExceptionA();
 finally 
    throw new ExceptionB();

ExceptionA 在 Exception B 之前被抛出。ExceptionA 将被抑制。

但是在try-with-resource中,像这样,

try ( // declare resource that throw ExceptionA in close method ) 
    throw new ExceptionB();

ExceptionA 在 ExceptionB 之后抛出。 ExceptionA 将被抑制。

为什么它们有不同的异常抑制顺序?

【问题讨论】:

【参考方案1】:

当你使用 try 和 finally 而不使用 try-with-resources 时,当 try 块中出现问题时,你会抛出异常,然后执行 finally,如果在 finally 块中抛出异常,则异常finally 掩码抛出的异常是try块抛出的异常。 “异常屏蔽”是 JVM 选择从 try-finally 抛出的异常是 finally 块中的异常,而不是原始异常。这可能非常糟糕,因为 try 块抛出的异常是带有错误信息的异常,finally 抛出的异常通常只是噪音。因此,在上面的示例中,除了 A 异常,从实现者的角度来看,发生的事情是直观的,但对应用程序开发人员没有用;关于实际问题的有价值信息在 A 中,而您丢失了它,而抛出的是 B,而 B 的堆栈跟踪是您在阅读日志文件时看到的。

例如:我的代码进行了一个 JDBC 调用,该调用引发了一个异常,其中一个行号告诉我错误发生在哪里,一个 SQLState 我可以映射回供应商代码告诉我出了什么问题,但是当关闭语句时或者连接存在网络故障,因为 JDBC 对象告诉服务器服务器应该清理什么,我得到一个损坏的管道异常。除非您对异常处理非常彻底,否则很容易用破管异常来掩盖有用的异常,即您无能为力且不关心的异常。

try-with-resources 功能试图确保信息性异常不会被关闭时抛出的偶然异常所掩盖,try 块中抛出的异常是被抛出的异常,并且是由关闭方法抛出的异常。 way out 被抑制,这意味着它被从 try 块添加到异常中(除非在 try 块中没有抛出任何东西,在这种情况下,关闭时抛出的异常就是抛出的异常)。

所以这是一个变化,但它在减少有价值的异常被无意中掩盖的机会方面是一个很大的改进。

【讨论】:

那么你的意思是try-final例子中不自然的抑制顺序是Java早期的糟糕设计吗? @zhiyuany:是的,这是 Java 设计方式的不幸后果。您可以通过非常精确的异常处理来解决它,但很容易搞砸。【参考方案2】:

C++ 异常处理范式的一个弱点是,它主要被 Java 继承,继而被 .NET 继承,它不能有效地处理需要清理(堆栈展开)的异常情况以及展开过程第一个异常的堆栈触发第二个异常。在 C++ 中,第二个异常基本上会杀死一切。 Java 和 .NET 的设计者显然不喜欢这种行为,但是这两个框架都没有办法同时解除两个异常。 Java 的设计者决定,当第二个异常被抛出时,最不坏的行为是让系统放弃第一个异常(以及任何从它展开堆栈的尝试),但即使它是“最少的坏事” “它仍然很邪恶。 .NET 的实现者遵循 Java 模式。

要真正干净地处理这种情况,需要提供清理代码,其中包含有关它是否正在运行以响应异常的信息,如果是,它是什么。如果清理代码中发生异常情况,这些信息将使清理代码可以确保如果它失败,它只会替换更重要的早期异常,并确保早期异常的证据保留在任何事件。不幸的是,除了在catchtry 块中重复代码,没有标准的约定可以让清理代码知道它运行的原因。

“使用资源尝试”构造假定首先发生的异常对调用代码更可能是重要的,尽管它保留了清理时发生的异常的证据。这不如允许清理代码确定其异常是否比原始异常重要,但它比让清理代码破坏早期异常的所有证据,或者完全避免抛出异常要好,因为它可以“不知道什么时候能够在不扼杀其他例外的情况下这样做。

【讨论】:

【参考方案3】:

这是因为在try-with-resources 语句中,所有资源在try 块之后立即关闭,即使try 块抛出异常

try (
    // resource a which throws A when closed
) 
    // exception B thrown from here
 // resource a closed HERE

这意味着B被抛出firstAsecond是完全合乎逻辑的。

因此,A 将被“抑制”(附加到)B,而不是相反。


当然,如果您可以首先打开资源a,则这是正确的;如果打开它会抛出异常,B 将根本没有机会被抛出......

【讨论】:

对不起,我没有说清楚。在第二个示例中,我的意思是从 close 方法抛出异常。 是的,但不是。看我的回答。在 try 块中抛出异常这一事实并不能阻止资源被关闭! 哦,我明白你的意思了。就像@Nathan 所说,自然顺序应该是早期异常抑制后来的异常。那么为什么在 try-finally 示例中,异常以相反的顺序被抑制?是设计不好还是有其他原因? 可能是因为保证finally块会被执行;并且由于在这种情况下finally 会引发异常,因此它将从之前的任何代码中收集异常。至少这是我的猜测。

以上是关于为啥以相反的执行顺序处理 try-with-resource 的抑制异常?的主要内容,如果未能解决你的问题,请参考以下文章

为啥线程味精以相反的顺序出现?

为啥这两个变量之一以相反的顺序打印到文件中?

为啥添加两个 .OrderBy(或 .OrderByDescending)语句会以相反的顺序应用排序?

我在运行代码时遇到错误,该代码接受用户的温度并以相反的顺序打印,为啥会这样?

为啥合并打印顺序相反

为啥 std::bitset 的位顺序相反? [复制]