不抛出异常类型处理的问题 - 需要更多通用版本的multi-catch

时间:2014-09-26 20:17:43

标签: java exception generics java-7 checked-exceptions

抱歉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
  1. 如果围绕外部方法调用的代码会引发运行时异常怎么办?现在我没有简单的方法来区分异常调用是否来自对外部代码的实际调用,或其他什么!我在尝试这个设计时遇到了这个问题。代码从其他地方抛出IllegalArgumentException,我的代码处理将它视为从SomeReturnType externalCodeCallGetSomeReturnValue(条形栏)抛出它。这似乎是一个代码健康问题,这就是我放弃这个解决方案的原因。
  2. 我使用的解决方案是将异常存储为异常。但是,我讨厌丢失那种类型的信息。没有额外的代码工作,如果想要抛出它,它将必须声明“抛出异常”,这是不好的,类似的代码健康问题。有没有办法处理这种情况? 我最终做的是让它以我想要的方式工作如下:

      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的答案会很有趣,但这并不能解决我正在处理的问题。

1 个答案:

答案 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
}