如何检查流实例是否已被使用(意味着已经调用了终端操作,使得对终端操作的任何进一步调用都可能因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);
因此对于这些流,实现这种方法似乎并不那么困难。
答案 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()
上调用Stream
。 IntStream.range(0,1000)
(没有任何过滤步骤等)将不会消耗该流,而是通过Spliterator<T>.estimateSize()
返回该流的基础已知长度!