为什么以相反的执行顺序处理 try-with-resource 的抑制异常?
Why suppressed exception of try-with-resource is handled in a reverse order of execution?
在正常的 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 将被抑制。
为什么它们抑制异常的顺序不同?
这是因为在try-with-resources
语句中,在try块之后立即关闭所有资源,即使try块抛出异常:
try (
// resource a which throws A when closed
) {
// exception B thrown from here
} // resource a closed HERE
这意味着 B
被抛出是完全合乎逻辑的 first and A
second.
因此 A
将是 "suppressed by"(附加到)B
而不是相反。
当然,如果您首先可以打开资源a
,这也是正确的;如果打开它抛出异常,B
将根本没有机会被抛出...
当你使用 try 和 finally 而没有 try-with-resources 时,当 try 块中出现问题时,你会抛出异常,然后执行 finally,如果在 finally 块期间抛出异常则finally 掩码 try 块抛出的异常。 "Exception-masking" 是 JVM 选择从 try-finally 抛出的异常是来自 finally 块的异常,而不是原始异常。这可能非常糟糕,因为 try 块抛出的异常是包含错误信息的异常,而 finally 块抛出的异常通常只是噪音。所以在你上面的例子中有例外 A,从实现者的角度来看,发生的事情是直观的,但它对应用程序开发人员没有用;关于实际出错的有价值的信息在 A 中,您丢失了它,而抛出的是 B,而 B 的堆栈跟踪是您在阅读日志文件时看到的。
例如:我的代码进行了一个 JDBC 调用,该调用抛出一个异常,行号告诉我错误发生的位置,我可以映射回供应商代码的 SQLState 告诉我出了什么问题,但是当关闭语句或连接时出现网络故障,因为 JDBC 对象告诉服务器服务器应该清理什么,我得到一个破损的管道异常。除非您对异常处理非常彻底,否则很容易用损坏的管道掩盖有用的异常,即您无能为力且不关心的异常。
try-with-resources 功能试图确保提供信息的异常不会被关闭时抛出的偶然异常所掩盖,try 块中抛出的异常是被抛出的异常和 close 方法抛出的异常在出路时被抑制,这意味着它被添加到 try 块的异常中(除非 try 块中没有抛出任何东西,在这种情况下,关闭时抛出的异常就是抛出的异常)。
所以这是一个变化,但它在减少有价值的异常被无意中掩盖的可能性方面是一个很大的改进。
C++ 异常处理范例的一个弱点是,它无法有效地处理需要清理(堆栈展开)的异常情况,该范例主要由 Java 继承,而后由 .NET 继承。 ,并且从第一个异常中展开堆栈的过程会触发第二个异常。在 C++ 中,第二个异常基本上会杀死一切。 Java 和 .NET 的设计者显然不喜欢这种行为,但是这两个框架都没有任何方法可以同时解除两个异常。 Java 的设计者决定,当第二个异常被抛出时,最不邪恶的行为是让系统放弃第一个异常(以及任何从中展开堆栈的尝试),但即使它是"least of evils"还是很邪恶的。 .NET 的实现者遵循 Java 模式。
要真正干净地处理这种情况,需要提供清理代码,其中包含有关是否正在 运行 响应异常的信息,如果是,那是什么。如果清理代码中出现异常情况,此类信息将使清理代码有可能确保在失败时,如果它更重要,它只会替换较早的异常,并确保将较早异常的证据保留在任何事件。不幸的是,除了在 catch
和 try
块中重复代码之外,没有让清理代码知道它为什么是 运行 的标准约定。
"try with resources" 构造假设首先发生的异常对调用代码更重要,尽管它保留了清理时发生的异常的证据。这不如让清理代码确定它的异常是否比原始异常重要或不重要,但它比让清理代码销毁所有早期异常的证据,或者完全避免抛出异常要好,因为它可以不知道什么时候可以这样做而不会扼杀其他异常。
在正常的 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 将被抑制。
为什么它们抑制异常的顺序不同?
这是因为在try-with-resources
语句中,在try块之后立即关闭所有资源,即使try块抛出异常:
try (
// resource a which throws A when closed
) {
// exception B thrown from here
} // resource a closed HERE
这意味着 B
被抛出是完全合乎逻辑的 first and A
second.
因此 A
将是 "suppressed by"(附加到)B
而不是相反。
当然,如果您首先可以打开资源a
,这也是正确的;如果打开它抛出异常,B
将根本没有机会被抛出...
当你使用 try 和 finally 而没有 try-with-resources 时,当 try 块中出现问题时,你会抛出异常,然后执行 finally,如果在 finally 块期间抛出异常则finally 掩码 try 块抛出的异常。 "Exception-masking" 是 JVM 选择从 try-finally 抛出的异常是来自 finally 块的异常,而不是原始异常。这可能非常糟糕,因为 try 块抛出的异常是包含错误信息的异常,而 finally 块抛出的异常通常只是噪音。所以在你上面的例子中有例外 A,从实现者的角度来看,发生的事情是直观的,但它对应用程序开发人员没有用;关于实际出错的有价值的信息在 A 中,您丢失了它,而抛出的是 B,而 B 的堆栈跟踪是您在阅读日志文件时看到的。
例如:我的代码进行了一个 JDBC 调用,该调用抛出一个异常,行号告诉我错误发生的位置,我可以映射回供应商代码的 SQLState 告诉我出了什么问题,但是当关闭语句或连接时出现网络故障,因为 JDBC 对象告诉服务器服务器应该清理什么,我得到一个破损的管道异常。除非您对异常处理非常彻底,否则很容易用损坏的管道掩盖有用的异常,即您无能为力且不关心的异常。
try-with-resources 功能试图确保提供信息的异常不会被关闭时抛出的偶然异常所掩盖,try 块中抛出的异常是被抛出的异常和 close 方法抛出的异常在出路时被抑制,这意味着它被添加到 try 块的异常中(除非 try 块中没有抛出任何东西,在这种情况下,关闭时抛出的异常就是抛出的异常)。
所以这是一个变化,但它在减少有价值的异常被无意中掩盖的可能性方面是一个很大的改进。
C++ 异常处理范例的一个弱点是,它无法有效地处理需要清理(堆栈展开)的异常情况,该范例主要由 Java 继承,而后由 .NET 继承。 ,并且从第一个异常中展开堆栈的过程会触发第二个异常。在 C++ 中,第二个异常基本上会杀死一切。 Java 和 .NET 的设计者显然不喜欢这种行为,但是这两个框架都没有任何方法可以同时解除两个异常。 Java 的设计者决定,当第二个异常被抛出时,最不邪恶的行为是让系统放弃第一个异常(以及任何从中展开堆栈的尝试),但即使它是"least of evils"还是很邪恶的。 .NET 的实现者遵循 Java 模式。
要真正干净地处理这种情况,需要提供清理代码,其中包含有关是否正在 运行 响应异常的信息,如果是,那是什么。如果清理代码中出现异常情况,此类信息将使清理代码有可能确保在失败时,如果它更重要,它只会替换较早的异常,并确保将较早异常的证据保留在任何事件。不幸的是,除了在 catch
和 try
块中重复代码之外,没有让清理代码知道它为什么是 运行 的标准约定。
"try with resources" 构造假设首先发生的异常对调用代码更重要,尽管它保留了清理时发生的异常的证据。这不如让清理代码确定它的异常是否比原始异常重要或不重要,但它比让清理代码销毁所有早期异常的证据,或者完全避免抛出异常要好,因为它可以不知道什么时候可以这样做而不会扼杀其他异常。