Как ловить исключения в Java 8 Stream.flatMap (..) - PullRequest
0 голосов
/ 08 января 2019

Учитывая Stream и метод, который возвращает Stream для разных аргументов в качестве источника данных, я ищу способ объединения потоков через flatMap(..) и перехвата определенных Exceptions во время выполнения.

Давайте возьмем следующий фрагмент кода:

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

Есть ли способ отловить исключение каждого отдельного Stream, созданного getGenerator(..), и просто подавить Exception, заменив «поврежденный» Stream пустым, или пропустить эти элементы из удельный генератор Stream?

Ответы [ 3 ]

0 голосов
/ 08 января 2019

Можно обернуть Stream в другой, используя Spliterator. Этот метод защитит данный Stream, перехватив Exception и сохранив это состояние:

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

Тогда мы можем обернуть наш Stream метод и безопасно передать его в 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();
0 голосов
/ 08 января 2019

Обходной путь - заставить Stream, созданный getGenerator, быть оцененным в реализации метода flatMap. Это заставляет NullPointerException быть брошенным в блоке try - catch и, следовательно, может быть обработано.

Для этого вы можете collect Stream (например, List):

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

Включение этого в ваш оригинальный фрагмент:

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

Предупреждение : этот подход может снизить производительность, если getGenerator Stream будет лучше оценивать лениво.

0 голосов
/ 08 января 2019

Попробуйте это:

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

Тогда просто позвоните getGenerator:

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