Why does stream api is not designed for Exception handling?

最后都变了- 提交于 2019-12-09 04:57:20

问题


Fixtures

BiConsumer<Exception, Consumer<? super Integer>> NOTHING = (ex, unused) ->{/**/};

When I try to fix the bug that is reported by @Holger in this answer:

Stream<Integer> stream = Stream.of(1, 2, 3);

// v--- the bug I have already fixed, it will throws RuntimeException 
exceptionally(stream, NOTHING).collect(ArrayList::new, (l, x) -> {
    l.add(x);
    if (x < 4) throw new RuntimeException();
}, List::addAll);

Everything is ok but when using Stream.of(T) the map(...) operation will be invoked infinitely, for example:

List<Integer> result = exceptionally(
        //               v--- infinitely call
        Stream.of("bad").map(Integer::parseInt),
        NOTHING
).collect(toList());

But when I replace Stream.of(T) with Stream.of(T[]), it is works fine again, for example:

//            v--- return an empty list
List<Integer> result = exceptionally(
        Stream.of(new String[]{"bad"}).map(Integer::parseInt),
        NOTHING
).collect(toList());

The java.util.stream.Streams.StreamBuilderImpl#tryAdvance should be reset the count first, for example:

public boolean tryAdvance(Consumer<? super T> action) {
    Objects.requireNonNull(action);

    if (count == -2) {
        action.accept(first);
        count = -1;// <--- it should be call before `action.accept(first)`;
        return true;
    }
    else {
        return false;
    }
}

Q: It should be a bug in jdk, since it must keep the semantics consistent between the Stream.of methods. Am I right?

<T> Stream<T> exceptionally(Stream<T> source,
        BiConsumer<Exception, Consumer<? super T>> exceptionally) {

    class ExceptionallySpliterator extends AbstractSpliterator<T>
            implements Consumer<T> {

        private Spliterator<T> source;
        private T value;

        public ExceptionallySpliterator(Spliterator<T> source) {
            super(source.estimateSize(), source.characteristics());
            this.source = source;
        }

        @Override
        public boolean tryAdvance(Consumer<? super T> action) {
            Boolean state = attempt(action);
            if (state == null) return true;
            if (state) action.accept(value);
            return state;
        }

        private Boolean attempt(Consumer<? super T> action) {
            try {
                return source.tryAdvance(this);
            } catch (Exception ex) {
                exceptionally.accept(ex, action);
                return null;
            }
        }

        @Override
        public void accept(T value) {
            this.value = value;
        }
    }

    return stream(
            new ExceptionallySpliterator(source.spliterator()), 
            source.isParallel()
    ).onClose(source::close);
}

回答1:


I wouldn’t call this a bug—not even an unexpected behavior, given the fact that I warned about such scenarios in this comment on the linked question a month ago:

Keep in mind that you don’t know whether the source iterator actually advanced its internal state or not when an exception has been thrown, so assuming that there is a next element can bring you into an infinite loop, repeating the failed operation over and over again.

Of course, for the Stream.of(singleElement) case that scenario is easy to avoid and changing the order of the two statements, action.accept(first); and count = -1;, would make the code more robust, still, being able to recover from an exception is not a guaranteed feature at all and there are other stream sources for which such recovery couldn’t get implemented that easily.

E.g., the streams returned by BufferedReader.lines(), resp. Files.lines() can’t force their underlying reader to advance one line if an IOException occurs.

Generally, this attempt to recover from an exception makes the premature assumption that an exception thrown by the source always indicates a problem with a particular element rather than a problem with the source in general. This works with the contrived example where you know that the exception is associated with a particular element because you provoked it. But this is not how you can deal with unexpected exceptions, which is what exceptions usually are, as when you expect a particular problem, like string elements not being in number format, you should handle them straight-forwardly, like filtering out the invalid strings before parsing.



来源:https://stackoverflow.com/questions/45256232/why-does-stream-api-is-not-designed-for-exception-handling

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!