【问题标题】:Aggregate runtime exceptions in Java 8 streams在 Java 8 流中聚合运行时异常
【发布时间】:2015-07-18 23:23:11
【问题描述】:

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

class ABC {

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

    public void doStuffOnList(List<MyObject> myObjs) {
        try {
            myObjs.stream().forEach(ABC:doStuff);
        } catch(AggregateRuntimeException??? are) {
            ...
        }             
    }
}

现在我希望处理列表中的所有项目,并将单个项目上的任何运行时异常收集到一个“聚合”运行时异常中,该异常将在最后抛出。

在我的真实代码中,我进行了可能引发运行时异常的第 3 方 API 调用。我想确保所有项目都得到处理并在最后报告任何错误。

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

【问题讨论】:

  • 这感觉像是在滥用异常机制。您在这里的真正用例是什么?验证一组操作并报告失败的原因?
  • @Kayaman 不完全是。我正在进行可能引发运行时异常的第 3 方 API 调用。我要做的就是确保所有项目都得到处理并在最后报告任何错误。
  • 你真的需要使用流吗?没有它,这是微不足道的。
  • @Kayaman 我知道,对吧?但我希望最终能使用parallelStream
  • 我的 stream-fu 不太强大,无法给你一个很好的答案,但基于 this discussion on streams and exceptions,基于流的解决方案最终会至少有点狡猾。

标签: java exception-handling java-8 java-stream


【解决方案1】:

在这种doStuff 方法为void 并且您只关心异常的简单情况下,您可以保持简单:

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

【讨论】:

  • 我喜欢你的答案的早期版本 :) 但是嘿,总是有编辑历史......
【解决方案2】:

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

假设您想要映射您的值并跟踪所有异常:

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

让我们有一些输入:

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

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

Map<Boolean, List<Try<String>>> 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  

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

这是一个带有完整示例的gist

【讨论】:

  • 谢谢!我会看看这个。
【解决方案3】:

这是映射到异常的主题的变体。

从您现有的doStuff 方法开始。注意,这符合功能接口Consumer&lt;MyObject&gt;

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

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

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

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

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

【讨论】:

  • 使用这种方法而不是仅仅从高阶函数返回RuntimeException并使用map()而不是flatMap()有什么好处?
  • @metacubed 映射器函数总是要返回一些东西。如果没有例外,它是否应该返回null?然后空值会出现在结果列表中,或者它们必须被过滤掉。
【解决方案4】:

我能想象的唯一可能的方法是将列表中的值映射到 monad,这将代表您的处理执行结果(值成功或 throwable 失败)。然后将您的流折叠成具有聚合值列表的单个结果或一个带有先前步骤中抑制的列表的异常。

public Result<?> doStuff(List<?> list) {
     return list.stream().map(this::process).reduce(RESULT_MERGER)
}

public Result<SomeType> 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<?>> RESULT_MERGER = (left, right) -> left.merge(right)

结果实现可能会有所不同,但我想你明白了。

【讨论】:

  • 是的。我开始使用自己滚动的结果映射器,但希望有更好的方法。
  • someValue是进程的参数吗?如果是这样,你能解决它吗?
  • 您可以声明SomeType result 之类的内容,然后进行处理,将listItem 转换为result,然后返回Result.success(result)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-05-26
  • 1970-01-01
  • 1970-01-01
  • 2018-04-29
  • 1970-01-01
  • 2017-09-26
  • 1970-01-01
相关资源
最近更新 更多