提问人:stonar96 提问时间:11/8/2023 最后编辑:stonar96 更新时间:11/9/2023 访问量:86
Java 中的嵌套 try-finally 块、等价和保证
Nested try-finally blocks, equivalence and guarantees in Java
问:
最近我正在研究一种“清理”方法,我想确保尝试执行每个语句。我的第一种方法是这样的(语句在哪里):s#;
try {
s1;
} finally {
try {
s2;
} finally {
s3;
}
}
为简单起见,我在这里省略了异常处理部分(块),我在其中存储了第一个出现和随后被抑制的 s,以便稍后重新抛出。catch
Throwable
然后我注意到还有另一种(也许更正确?)方法可以做同样的事情:
try {
try {
s1;
} finally {
s2;
}
} finally {
s3;
}
在我看来,第二种方法感觉更正确,因为一旦尝试,我们已经在两个区块内,这保证了所有区块都将被执行。s1;
try
finally
我的问题是:就 JVM 提供的保证而言,这些方法之间实际上存在差异吗?
此外,从理论上讲,JVM 是否有可能在第一个代码示例中抛出 an(参见下面的代码),以便不再尝试内部 - 块?Error
// not a statement
try
finally
try {
s1;
} finally {
// not a statement
try {
s2;
} finally {
s3;
}
}
根据 11.1.3。异步异常,异常基本上可以在任何地方抛出。这就是为什么我认为嵌套 - 块是必要的的主要原因,如第二个代码示例所示。还是我理解了 JLS 错误,只有在也有语句的情况下才会发生异常?try
finally
答:
将 an 和 an 添加到组合中,它变得比现在更清楚,这是一个难以维护的混乱,需要注释解释为什么你有这种奇怪的嵌套尝试块组合。s4
s5
因此,我建议你这样做:
Throwable t = null;
try { s1(); } catch (Throwable e) {t = e;}
try { s2(); } catch (Throwable e) {t = e;}
try { s3(); } catch (Throwable e) {t = e;}
if (t != null) throw t;
您可以花一些时间,并用实用程序方法()替换,如果已经发生异常,则将它们附加为抑制异常,就像try-finally一样。t = e;
t = update(t, e);
或者,更好的是,添加 lambda 支持:
public final class Cleanup {
public interface CleanupJob<T extends Throwable> {
void cleanup() throws T;
}
@SafeVarargs
public static <T extends Throwable> void cleanup(CleanupJob<? extends T>... jobs) {
Throwable ex = null;
for (var job : jobs) try {
job.cleanup();
} catch (Exception e) {
if (ex == null) {
ex = e;
} else {
ex.addSuppressed(e);
}
}
if (ex != null) throw (T) ex;
}
}
要使用:
import static CleanupRunner.cleanup;
...
cleanup(
() -> s1(),
() -> s2(),
() -> s3(),
() -> s4());
// or even
cleanup(
this::s1,
ref::s2,
SomeClass::staticS3
);
此外,从理论上讲,JVM 在第一个代码示例中抛出 Error 是否可能 // 而不是语句(请参阅下面的代码),从而不再尝试内部 try 块?
事实并非如此。
评论
try
finally
"...我想确保每个语句都尝试执行(全部或根本不执行)。..."
在前面,使用提供的示例,将执行所有语句。
不会有,“......或者根本没有”。
使用布尔值,这是一个示例。
boolean exit = false;
try {
} catch (Throwable t) {
exit = true;
} finally {
if (!exit) {
try {
} catch (Throwable t) {
exit = true;
} finally {
}
}
}
通常,最终代码块用于简单地应用最终过程。
在某些情况下,这实际上是必要的,例如,关闭 Closeable 对象。
实际上,这就是实现 AutoCloseable 接口和 try-with-resources 语法的原因。
int value;
try {
value = Integer.parseInt("abc");
} finally {
value = 123;
}
换句话说,虽然您的代码是允许的并且有效,但您通常不希望继续嵌套 try 代码块。
只需重新评估您当前的设计和控制流程即可。
"...然后我注意到还有另一种(也许更正确?)方法可以做同样的事情......”
没错,此示例将提供相同的功能。
而且,我想解释器会为每个编译一个类似的“字节码”。
"...就 JVM 提供的保证而言,这些方法之间实际上存在差异吗?..."
保证是一样的;抽象是一样的。
每个最终方块都会被执行,无论是否投掷了 Throwable;与 Catch 块不同。
"...从理论上讲,JVM是否有可能在第一个代码示例中抛出
错误
...这样内部的尝试块就不再尝试
了?..."
同样,如果在该行抛出 Error,则不会执行任何后续代码。
该程序将简单地停止并退出。
您还需要将该代码放在 try-catch 块中。
try {
} finally {
try {
throw new Error();
} finally {
}
}
评论