简体   繁体   中英

Why does stream api is not designed for Exception handling?

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

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.

Eg, 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.

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