为什么stream api不是为异常处理而设计的?

时间:2017-07-22 15:30:00

标签: java java-8 java-stream

赛程

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

当我尝试修复@Holger在此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);

一切正常但使用Stream.of(T)时,map(...)操作将无限调用,例如:

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

但是当我用Stream.of(T)替换Stream.of(T[])时,它再次正常工作,例如:

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

java.util.stream.Streams.StreamBuilderImpl#tryAdvance应先重置count,例如:

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 :它应该是jdk中的错误,因为它必须保持Stream.of方法之间的语义一致。我是对的吗?

<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 个答案:

答案 0 :(得分:4)

我不会称这是一个错误,甚至不是一个意外的行为,鉴于我警告过这样的场景this comment在一个月前的链接的问题:

  

请记住,当抛出异常时,您不知道源迭代器是否实际提升了其内部状态,因此假设存在下一个元素可能会导致无限循环,重复失败的操作一遍又一遍。

当然,对于Stream.of(singleElement)情况,这种情况很容易避免,并且更改两个语句action.accept(first);count = -1;的顺序,会使代码更加健壮,能够从异常中恢复并不是一个有保证的功能,并且还有其他流源,这些恢复无法轻易实现。

,例如BufferedReader.lines()返回的流。如果发生Files.lines(),则IOException无法强制其基础读者前进一行。

通常,这种尝试从异常中恢复,使过早假设由源引发的异常总是指示与特定的元件,而不是与整个源的问题的问题。这适用于设计的示例,您知道异常与特定元素相关联,因为您激发了它。但这不是你如何处理意外的异常,这是异常通常是什么,因为当你期望一个特定的问题,比如字符串元素不是数字格式时,你应该直接处理它们,就像在解析之前过滤掉无效字符串一样。