请注意,捕获Throwable 的频率比您可能意识到的要频繁。其中一些案例与 Java 语言功能紧密结合,这些功能可能会产生与您所展示的非常相似的字节码。
首先,由于finally 在字节码级别上没有挂起,它通过为Throwable 安装异常处理程序来实现,该处理程序将在重新抛出Throwable 之前执行finally 块的代码,如果代码流达到了这一点。此时你可能会做一些非常糟糕的事情:
try
{
throw new OutOfMemoryError();
}
finally
{
// highly discouraged, return from finally discards any throwable
return;
}
结果:
什么都没有
try
{
throw new OutOfMemoryError();
}
finally
{
// highly discouraged too, throwing in finally shadows any throwable
throw new RuntimeException("has something happened?");
}
结果:
java.lang.RuntimeException: has something happened?
at Throwables.example2(Throwables.java:45)
at Throwables.main(Throwables.java:14)
当然,finally 也有合法的用例,比如进行资源清理。使用类似字节码模式的相关构造是synchronized,它将在重新抛出之前释放对象监视器:
Object lock = new Object();
try
{
synchronized(lock) {
System.out.println("holding lock: "+Thread.holdsLock(lock));
throw new OutOfMemoryError();
}
}
catch(Throwable t) // just for demonstration
{
System.out.println(t+" has been thrown, holding lock: "+Thread.holdsLock(lock));
}
结果:
holding lock: true
java.lang.OutOfMemoryError has been thrown, holding lock: false
try-with-resource 语句更进一步;它可能会通过记录close() 操作抛出的后续抑制异常来修改挂起的 throwable:
try(AutoCloseable c = () -> { throw new Exception("and closing failed too"); }) {
throw new OutOfMemoryError();
}
结果:
java.lang.OutOfMemoryError
at Throwables.example4(Throwables.java:64)
at Throwables.main(Throwables.java:18)
Suppressed: java.lang.Exception: and closing failed too
at Throwables.lambda$example4$0(Throwables.java:63)
at Throwables.example4(Throwables.java:65)
... 1 more
另外,当你submit一个任务到ExecutorService时,所有的throwables都会被捕获并记录在返回的future中:
ExecutorService es = Executors.newSingleThreadExecutor();
Future<Object> f = es.submit(() -> { throw new OutOfMemoryError(); });
try {
f.get();
}
catch(ExecutionException ex) {
System.out.println("caught and wrapped: "+ex.getCause());
}
finally { es.shutdown(); }
结果:
caught and wrapped: java.lang.OutOfMemoryError
在 JRE 提供执行器服务的情况下,责任在于 FutureTask,这是内部使用的默认 RunnableFuture。我们可以直接演示该行为:
FutureTask<Object> f = new FutureTask<>(() -> { throw new OutOfMemoryError(); });
f.run(); // see, it has been caught
try {
f.get();
}
catch(ExecutionException ex) {
System.out.println("caught and wrapped: "+ex.getCause());
}
结果:
caught and wrapped: java.lang.OutOfMemoryError
但CompletableFuture 表现出捕获所有可投掷物的类似行为。
// using Runnable::run as Executor means we're executing it directly in our thread
CompletableFuture<Void> cf = CompletableFuture.runAsync(
() -> { throw new OutOfMemoryError(); }, Runnable::run);
System.out.println("if we reach this point, the throwable must have been caught");
cf.join();
结果:
if we reach this point, the throwable must have been caught
java.util.concurrent.CompletionException: java.lang.OutOfMemoryError
at java.base/java.util.concurrent.CompletableFuture.encodeThrowable(CompletableFuture.java:314)
at java.base/java.util.concurrent.CompletableFuture.completeThrowable(CompletableFuture.java:319)
at java.base/java.util.concurrent.CompletableFuture$AsyncRun.run(CompletableFuture.java:1739)
at java.base/java.util.concurrent.CompletableFuture.asyncRunStage(CompletableFuture.java:1750)
at java.base/java.util.concurrent.CompletableFuture.runAsync(CompletableFuture.java:1959)
at Throwables.example7(Throwables.java:90)
at Throwables.main(Throwables.java:24)
Caused by: java.lang.OutOfMemoryError
at Throwables.lambda$example7$3(Throwables.java:91)
at java.base/java.util.concurrent.CompletableFuture$AsyncRun.run(CompletableFuture.java:1736)
... 4 more
所以底线是,您不应该关注Throwable 是否会被捕获的技术细节,而是代码的语义。这是用于忽略异常(坏)还是用于尽管报告了严重的环境错误(坏)还是仅用于执行清理(好)?上面描述的大多数工具都可以用于好的和坏的......