检查是否已消耗Java流

时间:2019-07-08 01:58:48

标签: java java-stream

如何检查流实例是否已被使用(意味着已经调用了终端操作,使得对终端操作的任何进一步调用都可能因IllegalStateException: stream has already been operated upon or closed.而失败?

理想情况下,我想要一个不消耗流的方法,如果该流尚未被消耗,则返回一个布尔值false,如果流已经被消耗而没有从流方法中捕获IllegalStateException(因为使用异常)控制流非常昂贵且容易出错,尤其是在使用标准异常时。

类似于Iterator中的hasNext()的一种方法,它具有异常抛出和布尔返回行为(尽管没有与next()签约)。

示例:

public void consume(java.util.function.Consumer<Stream<?>> stream) {
   consumer.accept(stream);
   // defensive programming, check state
   if (...) {
       throw new IllegalStateException("consumer must call terminal operation on stream");
   }
}

目标是如果客户端代码在不消耗流的情况下调用此方法,则会尽早失败。

似乎没有办法做到这一点,我必须添加一个try-catch块来调用诸如iterator()之类的任何终端操作,捕获异常并抛出新异常。

一个可以接受的答案也可以是“没有解决方案”,并且有充分的理由说明为什么规范不能添加这种方法(如果存在充分的理由)。似乎JDK流通常在其终端方法的开头具有以下代码段:

// in AbstractPipeline.java
if (linkedOrConsumed)
    throw new IllegalStateException(MSG_STREAM_LINKED);

因此对于这些流,实现这种方法似乎并不那么困难。

3 个答案:

答案 0 :(得分:2)

考虑到spliterator(例如)是终端操作,您可以简单地创建如下方法:

private static <T> Optional<Stream<T>> isConsumed(Stream<T> stream) {

    Spliterator<T> spliterator;
    try {
        spliterator = stream.spliterator();
    } catch (IllegalStateException ise) {
        return Optional.empty();
    }

    return Optional.of(StreamSupport.stream(
        () -> spliterator,
        spliterator.characteristics(),
        stream.isParallel()));
}

我不知道有什么更好的方法...用法是:

Stream<Integer> ints = Stream.of(1, 2, 3, 4)
                                 .filter(x -> x < 3);

YourClass.isConsumed(ints)
         .ifPresent(x -> x.forEachOrdered(System.out::println));

由于我认为不存在返回已消耗的Stream的实际原因,因此我返回Optional.empty()

答案 1 :(得分:0)

一种解决方案可以是在将中间操作(例如filter())传递到stream之前,先将其添加到consumer。在该操作中,除了保存状态(调用了该操作(例如,使用AtomicBoolean))之外,您什么也不做:

public void consume(Consumer<Stream<T>>consumer) {
    AtomicBoolean consumed = new AtomicBoolean(false);
    consumer.accept(stream.filter(i -> {
        consumed.set(true);
        return true;
    }));
    if (!consumed.get()) {
        throw new IllegalStateException("consumer must call terminal operation on stream");
    }
}

侧面说明:请勿为此使用peek(),因为不会通过短路端子操作(例如findAny())来调用它。

答案 2 :(得分:0)

这是一个独立的可编译解决方案,它使用委派的自定义Spliterator<T>实现+ AtomicBoolean来完成您想要的目标,而又不会丢失线程安全性或影响Stream<T>的并行性。

主要条目是Stream<T> track(Stream<T> input, Consumer<Stream<T>> callback)函数-您可以在回调函数中执行任何所需的操作。我首先修改了一个委派的Stream<T>实现,但是它太大了,无法进行委派而没有任何问题(请参阅我的代码注释,甚至Spliterator<T>都在委派时有所警告):

import java.util.Spliterator;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

class StackOverflowQuestion56927548Scratch {

    private static class TrackingSpliterator<T> implements Spliterator<T> {
        private final AtomicBoolean tracker;
        private final Spliterator<T> delegate;
        private final Runnable callback;

        public TrackingSpliterator(Stream<T> forStream, Runnable callback) {
            this(new AtomicBoolean(true), forStream.spliterator(), callback);
        }

        private TrackingSpliterator(
                AtomicBoolean tracker,
                Spliterator<T> delegate,
                Runnable callback
        ) {
            this.tracker = tracker;
            this.delegate = delegate;
            this.callback = callback;
        }

        @Override
        public boolean tryAdvance(Consumer<? super T> action) {
            boolean advanced = delegate.tryAdvance(action);
            if(tracker.compareAndSet(true, false)) {
                callback.run();
            }
            return advanced;
        }

        @Override
        public Spliterator<T> trySplit() {
            Spliterator<T> split = this.delegate.trySplit();
            //may return null according to JavaDoc
            if(split == null) {
                return null;
            }
            return new TrackingSpliterator<>(tracker, split, callback);
        }

        @Override
        public long estimateSize() {
            return delegate.estimateSize();
        }

        @Override
        public int characteristics() {
            return delegate.characteristics();
        }
    }

    public static <T> Stream<T> track(Stream<T> input, Consumer<Stream<T>> callback) {
        return StreamSupport.stream(
                new TrackingSpliterator<>(input, () -> callback.accept(input)),
                input.isParallel()
        );
    }

    public static void main(String[] args) {
        //some big stream to show it works correctly when parallelized
        Stream<Integer> stream = IntStream.range(0, 100000000)
                .mapToObj(Integer::valueOf)
                .parallel();
        Stream<Integer> trackedStream = track(stream, s -> System.out.println("consume"));

        //dummy consume
        System.out.println(trackedStream.anyMatch(i -> i.equals(-1)));
    }
}

只需返回track函数的流,也许改用callback参数类型(您可能不需要传递流)就可以了。

请注意,此实现仅跟踪实际消耗流的时间,并在例如由以下示例生成的.count()上调用StreamIntStream.range(0,1000)(没有任何过滤步骤等)将不会消耗该流,而是通过Spliterator<T>.estimateSize()返回该流的基础已知长度!