我已经深入研究了这个问题,很混乱。在Java
5、6或7中没有简单的答案。除了您指出的笨拙,冗长和脆弱之外,您的解决方案实际上还存在一个问题,
ExecutionException即您在调用时要剥离的代码
getCause()实际上包含了大多数内容。重要的堆栈跟踪信息!
也就是说,在您提供的代码中执行该方法的线程的所有堆栈信息仅在ExcecutionException中,而不在嵌套原因中,而嵌套原因仅覆盖
call()Callable中的帧。也就是说,您的
doSomethingWithTimeout方法甚至不会出现在您抛出的异常的堆栈跟踪中!您只会从执行程序中获得无实体的堆栈。这是因为
ExecutionException是唯一在调用线程上创建的(请参阅
FutureTask.get())。
我知道的唯一解决方案很复杂。很多问题起源与自由派异常规范的
Callable-
throwsException。您可以定义新的变体,
Callable其中的新变体可以精确地指定它们引发的异常,例如:
public interface Callable1<T,X extends Exception> extends Callable<T> { @Override T call() throws X; }这使执行可调用对象的方法具有更精确的
throws子句。如果要支持最多N个异常的签名,很遗憾,您将需要此接口的N个变体。
现在,您可以围绕JDK编写包装,该包装
Executor采用增强的Callable并返回增强的功能
Future,例如番石榴的CheckedFuture。被检查的异常类型在编译时从的创建和类型传播
ExecutorService到返回的
Futures,并最终
getChecked在将来使用该方法。
这就是您通过编译时类型安全性的方式。这意味着,而不是调用:
Future.get() throws InterruptedException, ExecutionException;
您可以致电:
CheckedFuture.getChecked() throws InterruptedException, ProcessExecutionException, IOException
因此避免了展开问题-您的方法立即引发所需类型的异常,并且这些异常在编译时可用并进行了检查。
在内部
getChecked,但是您 仍然
需要解决上述“遗漏原因”展开问题。您可以通过将(调用线程的)当前堆栈缝合到引发的异常的堆栈上来实现此目的。这扩展了Java中堆栈跟踪的通常用法,因为单个堆栈跨线程扩展,但是一旦知道发生了什么,它就会起作用并且很容易理解。
另一种选择是创建 另一个
同样的事情作为一个被抛出的异常,并设定原为新的事业。您将获得完整的堆栈跟踪,并且原因关系将与它的工作方式相同
ExecutionException-但您将拥有正确的异常类型。但是,您将需要使用反射,并且不能保证它能正常工作,例如,对于没有构造函数的具有常规参数的对象。



