最近我正在研究一种“清理”方法,我想确保尝试执行每个语句(全部或根本不执行)。我的第一个方法是这样的(其中
s#;
是陈述):
try {
s1;
} finally {
try {
s2;
} finally {
s3;
}
}
为了简单起见,我省略了异常处理部分(
catch
块),我在其中存储第一次出现的和随后被抑制的Throwable
以便稍后重新抛出。
然后我注意到还有另一种(也许更正确?)方法可以做同样的事情:
try {
try {
s1;
} finally {
s2;
}
} finally {
s3;
}
在我看来,第二种方法感觉更正确,因为一旦尝试
s1;
,我们就已经在两个try
块内,这保证了所有finally
块都将被执行。
我的问题是:这些方法在 JVM 提供的保证方面实际上有区别吗?
理论上,JVM 在第一个代码示例中的
Error
处抛出 // not a statement
是否有可能(参见下面的代码),从而不再尝试内部 try
块?
try {
s1;
} finally {
// not a statement
try {
s2;
} finally {
s3;
}
}
第二个例子可以避免此类问题。
添加一个
s4
和一个 s5
到混合中,它变得比本来应该的更加清晰,这是一个难以维护的混乱,需要注释来解释为什么你有这种奇怪的嵌套 try 块组合。
因此,我建议您这样做:
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;
您可以想象一下,并将
t = e;
替换为实用方法 (t = update(t, e);
),如果已经发生异常,该方法会将它们附加为抑制的异常,就像 try-finally 的做法一样。
或者,更好的是,添加 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 在第一个代码示例中是否有可能在 // 不是语句处抛出错误(请参阅下面的代码),从而不再尝试内部 try 块?
不是。