简体   繁体   中英

How to catch exceptions within Java 8 Stream.flatMap(..)

Given a Stream and a method that returns a Stream for different arguments as data source, I'm looking for a way to merge the streams via flatMap(..) and catching certain Exceptions during the execution.

Let's take the following code snippet:

public class FlatMap {

    public static void main(final String[] args) {
        long count;

        // this might throw an exception
        count = Stream.of(0.2, 0.5, 0.99).flatMap(chance -> getGenerator(chance, 20)).count();

        // trying to catch the exception in flatMap() will not work
        count = Stream.of(0.2, 0.5, 0.99).flatMap(chance -> {
            try {
                return getGenerator(chance, 20);
            } catch (final NullPointerException e) {
                return Stream.empty();
            }
        }).count();

        System.out.println(count);
    }

    // !! we cannot change this method, we simply get a Stream
    static Stream<Object> getGenerator(final double chance, final long limit) {
        return Stream.generate(() -> {
            if (Math.random() < chance) return new Object();
            throw new NullPointerException();
        }).limit(limit);
    }
}

Is there any way to catch the exception of each individual Stream that was created by getGenerator(..) and simply suppress the Exception , replacing the "corrupted" Stream with an empty one or skip those elements from the specific generator Stream ?

It is possible to wrap the Stream into another using the Spliterator . This method will protect a given Stream by catching the Exception and saving this state:

    static <T> Stream<T> protect(final Stream<T> stream) {
        final Spliterator<T> spliterator = stream.spliterator();
        return StreamSupport.stream(
                new Spliterators.AbstractSpliterator<T>(Long.MAX_VALUE,
                           spliterator.characteristics() & ~Spliterator.SIZED) {

                    private boolean corrupted = false;

                    @Override
                    public boolean tryAdvance(final Consumer<? super T> action) {
                        if (!corrupted) try {
                            return spliterator.tryAdvance(action);
                        } catch (final Exception e) {
                            // we suppress this one, stream ends here
                            corrupted = true;
                        }
                        return false;
                    }
                }, false);
    }

Then we can wrap our Stream method and safely pass it in flatMap(..) :

// we protect the stream by a wrapper Stream
count = Stream.of(0.2, 0.5, 0.99)
              .flatMap(chance -> protect(getGenerator(chance, 20)))
              .count();

One work around is to force the Stream created by getGenerator to be evaluated within the flatMap method implementation. This forces the NullPointerException to be thrown within the try - catch block, and therefore, able to be handled.

To do this, you can collect the Stream (to a List for example):

getGenerator(chance, 20).collect(Collectors.toList()).stream()

Incorporating this into your original snippet:

public class FlatMap {

    public static void main(final String[] args) {
        long count;

        // trying to catch the exception in flatMap() will not work
        count = Stream.of(0.2, 0.5, 0.99)
            .flatMap(chance -> {
                try {
                    return getGenerator(chance, 20).collect(Collectors.toList()).stream();
                } 
                catch (final NullPointerException e) {
                    return Stream.empty();
                }
            })
            .count();

        System.out.println(count);
    }

    // !! we cannot change this method, we simply get a Stream
    static Stream<Object> getGenerator(final double chance, final long limit) {
        return Stream.generate(() -> {
            if (Math.random() < chance) return new Object();
            throw new NullPointerException();
        }).limit(limit);
    }
}

Warning : this approach may reduce performance if the getGenerator Stream would be better to evaluate lazily.

Try this:

static <T> Supplier<T> getOrNull(Supplier<T> supplier) {
    return () -> {
        try {
            return supplier.get();
        } catch (Throwable e) {
            return null;
        }
    };
}

static Stream<Object> getGenerator(final double chance, final long limit) {
    return Stream.generate(
                      getOrNull(
                          () -> {
                              if (Math.random() < chance) return new Object();
                              throw new NullPointerException(); 
                              // You can throw any exception here
                          })) 
                .limit(limit)
                .filter(Objects::isNull);
}

Then simply call getGenerator :

count = Stream.of(0.2, 0.5, 0.99)
              .flatMap(chance -> getGenerator(chance, 20))
              .count();

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM