抱歉TL; DR,但我觉得它需要一些解释,否则会被误解。
我有一个方法调用(通常是外部的)代码,我希望它有时会抛出一个RuntimeException,并使用可能抛出InterruptedException或ExecutionException的future,我希望能够返回一组有序的返回值从调用到抛出异常,以及抛出的异常。我写了一些有用的东西,但不幸的是,代码的外观让我觉得我做错了什么。我认为我真正想要的是多捕获是一个更通用的概念。这将允许非常干净的代码来解决它,有点像这样:
public class SomeResults {
private final Set<SomeReturnType> valuesReturned;
private final @Nullable RuntimeException | ExecutionException | InterruptedException exception;
public SomeResults(Set<SomeReturnType> valuesReturned, RuntimeException | ExecutionException exception {
this.valuesReturned = valuesReturned;
this.exception = exception;
}
public Set<SomeReturnType> getValuesReturned() {
return valuesReturned;
}
public @Nullable RuntimeException | ExecutionException | InterruptedException getException();
}
并且有一个方法可以调用外部代码 ...
generateResults(Bar bar) {
// Setup code
Set<SomeReturnType> valuesReturned = new LinkedHashSet<>();
...
// loop
{
// stuff
... // exceptions in this method should throw except for this one external code call
try {
valuesReturned.add(externalCodeCallGetSomeReturnValue(bar))
}
catch( RuntimeException | ExecutionException | InterruptedException e) {
return new MyResults(valuesReturned, e)
}
...
}
return new MyResults(valuesReturned, (RuntimeException | ExecutionException | InterruptedException) null);
}
随后做
SomeResults myResults = foo.generateResults(new Bar());
if(myResults.getException() != null) {
throw(myResults.getException);
}
等。请注意,我注意到总是希望立即重新抛出异常 - 这取决于谁使用这些结果他们想要用它们做什么。我可能会做类似
的事情try {
SomeResults myResults = foo.generateResults(new Bar());
Foobar Foobar = new Foobar(myResults);
}
catch(Exception e) {
// I don't want to see any exceptions from externalCodeCallGetSomeReturnValue(bar) here
...
}
当然,我可以在生成结果的函数中抛出异常,而不是捕获异常并将其作为结果返回。这有两个非常重要的问题: 1.现在返回值集将会很尴尬 - 我可能会将Set传递给需要“返回”结果的方法,并且它会修改该集合而不是返回集合。这允许在返回异常时该集合是最新的。例如
generateResults(Bar bar, Set<SomeReturnType> orderedListForMeToWrite) throws ExecutionException, InterruptedException
我使用的解决方案是将异常存储为异常。但是,我讨厌丢失那种类型的信息。没有额外的代码工作,如果想要抛出它,它将必须声明“抛出异常”,这是不好的,类似的代码健康问题。有没有办法处理这种情况? 我最终做的是让它以我想要的方式工作如下:
public static class SomeResults {
private final Set<SomeReturnType> orderedReturnValues;
private final @Nullable Exception exception;
AsyncEchoesResult(Set<SomeReturnType> responses) {
this.orderedResponses = responses;
this.exception = null;
}
AsyncEchoesResult(Set<SomeReturnType> responses, RuntimeException exception) {
this.orderedResponses = responses;
this.exception = exception;
}
AsyncEchoesResult(Set<SomeReturnType> responses, ExecutionException exception) {
this.orderedResponses = responses;
this.exception = exception;
}
AsyncEchoesResult(Set<SomeReturnType> responses, InterruptedException exception) {
this.orderedResponses = responses;
this.exception = exception;
}
public Set<SomeReturnType> getResponses() {
return orderedResponses;
}
public @Nullable Exception getException() {
return exception;
}
public void throwExceptionIfExists() throws ExecutionException, InterruptedException {
try {
throw (exception);
}
catch (RuntimeException | ExecutionException | InterruptedException e) {
throw e;
}
catch (Exception e) {
throw new RuntimeException("Unexpected exception type in SomeResults",e);
}
}
}
显然,这非常难看。如果我讨厌构造函数,我可以很容易地用一个带有Exception的单个替换它们,但是只能将类型检查弱化为throwException()的运行时调用。无论如何,是否有更好的替代方案?请注意,我正在使用JDK 7,所以虽然JDK 8的答案会很有趣,但这并不能解决我正在处理的问题。
答案 0 :(得分:1)
由于Java不允许将变量声明为“其中一种类型”,因此必须使用唯一支持这种类型集的构造来封装异常:抛出该异常的一段代码。
考虑以下类型定义:
interface ReThrower {
void reThrow() throws RuntimeException, ExecutionException, InterruptedException;
}
static class MyResult
{
private final Set<SomeReturnType> valuesReturned;
private final @Nullable ReThrower exception;
public MyResult(Set<SomeReturnType> valuesReturned, ReThrower exception) {
this.valuesReturned = valuesReturned;
this.exception = exception;
}
public Set<SomeReturnType> getValuesReturned() {
return valuesReturned;
}
public void reThrowException()
throws RuntimeException, ExecutionException, InterruptedException
{
if(exception!=null) exception.reThrow();
}
}
然后你可以像这样创建一个MyResult
:
MyResult generateResults(Bar bar) {
// Setup code
Set<SomeReturnType> valuesReturned = new LinkedHashSet<>();
// …
// loop
{
// stuff
// … exceptions in this method should throw except for this one external code call
try {
valuesReturned.add(externalCodeCallGetSomeReturnValue(bar));
}
catch( RuntimeException | ExecutionException | InterruptedException e) {
// In Java 8 you would say: new MyResult(valuesReturned, ()->{ throw e });
return new MyResult(valuesReturned, new ReThrower() {
public void reThrow()
throws RuntimeException, ExecutionException, InterruptedException {
throw e;
}
});
}
//...
}
return new MyResult(valuesReturned, null);
}
请注意,内部类(或Java 8中的lambda表达式)隐式存储异常,并且该隐式变量具有所需的“列出的异常类型之一”。然后,您可以安全地重新抛出异常:
MyResult results = new MultiCatchAndStore().generateResults(new Bar());
try
{
results.reThrowException();
} catch(RuntimeException | ExecutionException | InterruptedException ex)
{
// handle, of course, you could also have separate catch clauses here
}