0

很抱歉 TL;DR,但我觉得它需要一些解释,否则会被误解。

我有一个方法可以调用(通常是外部的)代码,我希望有时会抛出 RuntimeException,并使用可以抛出 InterruptedException 或 ExecutionException 的期货,并且我希望能够从调用直到抛出异常,以及抛出的异常。我写了一些有用的东西,但不幸的是,代码看起来让我觉得我做错了什么。我认为我真正想要的是 multi-catch 成为一个更通用的概念。这将允许非常干净的代码来解决它,有点像这样:

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
  1. 如果围绕外部方法调用的代码抛出运行时异常怎么办?现在我没有简单的方法来区分异常调用是来自对外部代码的实际调用,还是其他什么!我在尝试这种设计时实际上遇到了这个问题。该代码从其他地方抛出了 IllegalArgumentException,我的代码处理将其视为是从 SomeReturnType externalCodeCallGetSomeReturnValue(Bar bar) 中抛出的。这似乎是一个代码健康问题,这就是我放弃这个解决方案的原因。

我采用的解决方案是将异常存储为异常。但是,我讨厌丢失那种类型的信息。没有额外的代码工作,如果有东西想抛出它,它必须声明“抛出异常”,这不好,类似的代码健康问题。有没有好办法来处理这种情况?我最终要让它按照我想要的方式工作如下:

  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);
      }
    }
  }

显然,这很丑陋。如果我讨厌构造函数,我可以很容易地用一个接受异常的构造函数替换它们,但这会将类型检查削弱为仅运行时调用 throwException()。无论如何,有没有更好的替代品?请注意,我使用的是 JDK 7,所以虽然 JDK 8 的答案会很有趣,但这并不能解决我正在处理的问题。

4

1 回答 1

1

Since Java doesn’t allow declare a variable as “one of these types” you have to encapsulate the exception using the only construct which supports such a type set: a piece of code throwing that exception.

Consider the following type definitions:

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();
  }
}

Then you can create a MyResult like this:

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);
}

Note that the inner class (or lambda expression in Java 8) implicitly stores the exception and that that implicit variable has the desired “one of the listed exception type”. Then, you can safely re-throw the exception:

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
}
于 2014-09-27T08:08:14.643 回答