使用Stream.generate
无法创建有限流。
实现流的标准方法是通过实现Spliterator
,有时候使用迭代器。在任何情况下,实现都有一种报告结束的方式,例如当Spliterator.tryAdvance
返回false
或其forEachRemaining
方法返回时,或者在Iterator
源的情况下,当hasNext()
返回false
时。
Spliterator
甚至可以在处理开始之前报告预期的元素数量。
通过Stream
接口中的一个工厂方法创建的流(例如Stream.generate
)也可以实现为Spliterator
或使用流实现的内部功能,但无论如何实现,您都无法接触此实现以更改其行为,因此使这样的流变得有限的唯一方法是将limit
操作链接到流上。
如果要创建一个不由数组或集合支持且非空的有限流,并且没有任何现有的流源适用,则必须实现您自己的Spliterator
并创建一个流。如上所述,您可以使用现有方法将Iterator
转换为Spliterator
,但应抵制使用Iterator
的诱惑,因为它很熟悉。实现Spliterator
并不难:
static <T> Stream<T> generate(Supplier<T> s, long count) {
return StreamSupport.stream(
new Spliterators.AbstractSpliterator<T>(count, Spliterator.SIZED) {
long remaining=count;
public boolean tryAdvance(Consumer<? super T> action) {
if(remaining<=0) return false;
remaining--;
action.accept(s.get());
return true;
}
}, false);
}
从这个起点开始,您可以添加 Spliterator
接口的 default
方法的覆盖,权衡开发成本和潜在的性能改进,例如:
static <T> Stream<T> generate(Supplier<T> s, long count) {
return StreamSupport.stream(
new Spliterators.AbstractSpliterator<T>(count, Spliterator.SIZED) {
long remaining=count;
public boolean tryAdvance(Consumer<? super T> action) {
if(remaining<=0) return false;
remaining--;
action.accept(s.get());
return true;
}
@Override
public void forEachRemaining(Consumer<? super T> action) {
long toGo=remaining;
remaining=0;
for(; toGo>0; toGo--) action.accept(s.get());
}
}, false);
}
BufferedReader.lines()
是一个很好的例子。看看next()
方法和hasNext()
方法的实现,以及它们在调用之间如何保持状态。相比之下,Spliterator更加直观,只需要一个方法:tryAdvance(Consumer<? super String> c) { String line=readLine(); if(line==null) return false; c.accept(line); return true; }
就可以了。实现更简单(添加异常处理后仍然只有一半的代码大小),无需包装器... - HolgerStream.parallel()
的角度来看,它是安全的,因为它将正确地工作。Spliterator
本身不需要线程安全,因为流不会同时访问它。通过添加专用的trySplit
实现,可以提高并行性能,但请注意,只需使用IntStream.range(0, count).mapToObj(i -> supplier.get())
即可实现相同的结果。此答案的代码更像是解决更专业任务的模板。 - Holger