Java 8 流中的聚合运行时异常

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/30117134/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-11-02 16:30:33  来源:igfitidea点击:

Aggregate runtime exceptions in Java 8 streams

javaexception-handlingjava-8java-stream

提问by metacubed

Let's say I have a method which throws a runtime exception. I'm using a Streamto call this method on items in a list.

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

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) {
            ...
        }             
    }
}

Now I want all items in the list to be processed, and any runtime exceptions on individual items to be collected into an "aggregate" runtime exception which will be thrown at the end.

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

In my real code, I am making 3rd party API calls which may throw runtime exceptions. I want to make sure that all items are processed and any errors reported at the end.

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

I can think of a few ways to hack this out, such as a map()function which catches and returns the exception (..shudder..). But is there a native way to do this? If not, is there another way to implement it cleanly?

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

采纳答案by Misha

In this simple case where the doStuffmethod is voidand you only care about the exceptions, you can keep things simple:

在这个简单的情况下,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;
    });

However, if your requirements are more complicated and you prefer to stick with the standard library, CompletableFuturecan serve to represent "either success or failure" (albeit with some warts):

但是,如果您的要求更复杂并且您更喜欢坚持使用标准库,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);
            });
}

回答by Tagir Valeev

There are already some implementations of Trymonad for Java. I found better-java8-monadslibrary, for example. Using it, you can write in the following style.

已经有一些Try用于 Java的monad 实现。例如,我发现了Better-java8-monads库。使用它,你可以写成以下风格。

Suppose you want to map your values and track all the exceptions:

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

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

Let's have some input:

让我们有一些输入:

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

Now we can map them to successful tries and pass to your method, then collect successfully handled data and failures separately:

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

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

After that you can process successful entries:

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

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

And do something with all the exceptions:

并在所有例外情况下做一些事情:

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

The output is:

输出是:

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

I personally don't like how this library is designed, but probably it will be suitable for you.

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

Here's a gistwith complete example.

这是一个带有完整示例的要点

回答by Stuart Marks

Here's a variation on the theme of mapping-to-exceptions.

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

Start with your existing doStuffmethod. Note that this conforms to the functional interface Consumer<MyObject>.

从您现有的doStuff方法开始。请注意,这符合功能接口Consumer<MyObject>

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

Now write a higher-order function that wraps this and turns this into a function that might or might not return an exception. We want to call this from flatMap, so the way "might or might not" is expressed is by returning a stream containing the exception or an empty stream. I'll use RuntimeExceptionas the exception type here, but of course it could be anything. (In fact it might be useful to use this technique with checked exceptions.)

现在编写一个高阶函数来包装 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);
        }
    };
}

Now rewrite doStuffOnListto use this within a stream:

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

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

回答by SimY4

The only possible way I can imagine is to map values in a list to a monad, that will represent the result of your processing execution (either success with value or failure with throwable). And then fold your stream into single result with aggregated list of values or one exception with list of suppressed ones from the previous steps.

我能想象到的唯一可能的方法是将列表中的值映射到一个 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)

Result implementation may vary but I think you get the idea.

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