Aggregate runtime exceptions in Java 8 streams

Let's say I have a method which throws a runtime exception. I'm using a to call this method on items in a list.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) {
            ...
        }             
    }
}

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.

I can think of a few ways to hack this out, such as a 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()


答案 1

In this simple case where the method is and you only care about the exceptions, you can keep things simple:doStuffvoid

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

答案 2

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

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 gist with complete example.


推荐