Java 8流中的聚合运行时exception

假设我有一个抛出运行时exception的方法。 我正在使用Stream在列表中的项目上调用此方法。

 class ABC { public void doStuff(MyObject myObj) { if (...) { throw new IllegalStateException("Fire! Fear! Foes! Awake!"); } // do stuff... } public void doStuffOnList(List myObjs) { try { myObjs.stream().forEach(ABC:doStuff); } catch(AggregateRuntimeException??? are) { ... } } } 

现在我希望处理列表中的所有项目,并将各个项目的任何运行时exception收集到最终将抛出的“聚合”运行时exception中。

在我的真实代码中,我正在进行第三方API调用,这可能会引发运行时exception。 我想确保处理所有项目并在最后报告任何错误。

我可以想出一些方法来解决这个问题,例如map()函数捕获并返回exception( ..shudder .. )。 但有没有一种本地方式来做到这一点? 如果没有,是否还有另一种干净利落的方式?

在这个简单的情况下, doStuff方法是void ,你只关心exception,你可以保持简单:

 myObjs.stream() .flatMap(o -> { try { ABC.doStuff(o); return null; } catch (RuntimeException ex) { return Stream.of(ex); } }) // now a stream of thrown exceptions. // can collect them to list or reduce into one exception .reduce((ex1, ex2) -> { ex1.addSuppressed(ex2); return ex1; }).ifPresent(ex -> { throw ex; }); 

但是,如果您的要求更复杂并且您更愿意坚持使用标准库,则CompletableFuture可以用来表示“成功或失败”(尽管有一些瑕疵):

 public static void doStuffOnList(List myObjs) { myObjs.stream() .flatMap(o -> completedFuture(o) .thenAccept(ABC::doStuff) .handle((x, ex) -> ex != null ? Stream.of(ex) : null) .join() ).reduce((ex1, ex2) -> { ex1.addSuppressed(ex2); return ex1; }).ifPresent(ex -> { throw new RuntimeException(ex); }); } 

已经有一些Try monad for Java的实现。 例如,我找到了更好的java8-monads库。 使用它,您可以使用以下样式编写。

假设您要映射值并跟踪所有exception:

 public String doStuff(String s) { if(s.startsWith("a")) { throw new IllegalArgumentException("Incorrect string: "+s); } return s.trim(); } 

我们有一些意见:

 List input = Arrays.asList("aaa", "b", "abc ", " qqq "); 

现在我们可以将它们映射到成功的尝试并传递给您的方法,然后分别收集成功处理的数据和故障:

 Map>> result = input.stream() .map(Try::successful).map(t -> t.map(this::doStuff)) .collect(Collectors.partitioningBy(Try::isSuccess)); 

之后,您可以处理成功的条目:

 System.out.println(result.get(true).stream() .map(t -> t.orElse(null)).collect(Collectors.joining(","))); 

除了所有例外,做一些事情:

 result.get(false).stream().forEach(t -> t.onFailure(System.out::println)); 

输出是:

 b,qqq java.lang.IllegalArgumentException: Incorrect string: aaa java.lang.IllegalArgumentException: Incorrect string: abc 

我个人不喜欢这个库的设计,但它可能适合你。

这是一个完整的例子。

这是映射到exception主题的变体。

从现有的doStuff方法开始。 请注意,这符合function接口Consumer

 public void doStuff(MyObject myObj) { if (...) { throw new IllegalStateException("Fire! Fear! Foes! Awake!"); } // do stuff... } 

现在编写一个包含此函数的高阶函数,并将其转换为可能会或可能不会返回exception的函数。 我们想从flatMap调用它,因此表达“可能或不可能”的方式是返回包含exception或空流的流。 我将在这里使用RuntimeException作为exception类型,但当然它可以是任何东西。 (实际上,将此技术与已检查的exception一起使用可能很有用。)

  Function> ex(Consumer cons) { return t -> { try { cons.accept(t); return Stream.empty(); } catch (RuntimeException re) { return Stream.of(re); } }; } 

现在重写doStuffOnList以在流中使用它:

 void doStuffOnList(List myObjs) { List exs = myObjs.stream() .flatMap(ex(this::doStuff)) .collect(Collectors.toList()); System.out.println("Exceptions: " + exs); } 

我能想象的唯一可能的方法是将列表中的值映射到monad,这将代表处理执行的结果(使用值成功或使用throwable失败)。 然后将您的流折叠为具有聚合值列表的单个结果,或者将一个例外与前面步骤中的已抑制列表一起折叠。

 public Result doStuff(List list) { return list.stream().map(this::process).reduce(RESULT_MERGER) } public Result process(Object listItem) { try { Object result = /* Do the processing */ listItem; return Result.success(result); } catch (Exception e) { return Result.failure(e); } } public static final BinaryOperator> RESULT_MERGER = (left, right) -> left.merge(right) 

结果实施可能会有所不同,但我认为你明白了。