通常情况下,自定义操作需要处理 Spliterator
接口。它通过添加特征和大小信息以及拆分元素中的一部分作为另一个 Spliterator(因此得名)来扩展了 Iterator
的概念。它还通过只需要一个方法简化了迭代逻辑。
public static <T> Stream<T> takeWhile(Stream<T> s, Predicate<? super T> condition) {
boolean parallel = s.isParallel();
Spliterator<T> spliterator = s.spliterator();
return StreamSupport.stream(new Spliterators.AbstractSpliterator<T>(
spliterator.estimateSize(),
spliterator.characteristics()&~(Spliterator.SIZED|Spliterator.SUBSIZED)) {
boolean active = true;
Consumer<? super T> current;
Consumer<T> adapter = t -> {
if((active = condition.test(t))) current.accept(t);
};
@Override
public boolean tryAdvance(Consumer<? super T> action) {
if(!active) return false;
current = action;
try {
return spliterator.tryAdvance(adapter) && active;
}
finally {
current = null;
}
}
}, parallel).onClose(s::close);
}
为了保持流的属性,我们首先查询并重新建立新流的并行状态。同时,我们注册一个关闭操作,以关闭原始流。
主要工作是实现一个装饰前一个流状态的Spliterator。
除了SIZED和SUBSIZED之外,特性都被保留下来,因为我们的操作结果大小是不可预测的。原始大小仍然传递,现在将用作估计值。
此解决方案在操作期间存储传递给tryAdvance的Consumer,以便能够使用相同的适配器Consumer,避免为每个迭代创建新的Consumer。这是可行的,因为保证tryAdvance不会并发调用。
并行性是通过继承自AbstractSpliterator的分割完成的。这个继承的实现将缓冲一些元素,这是合理的,因为对于像takeWhile这样的操作,实现更好的策略真的很复杂。
所以你可以像这样使用它:
takeWhile(Stream.of("foo", "bar", "baz", "hello", "world"), s -> s.length() == 3)
.forEach(System.out::println);
将会打印出来
foo
bar
baz
或者
takeWhile(Stream.of("foo", "bar", "baz", "hello", "world")
.peek(s -> System.out.println("before takeWhile: "+s)), s -> s.length() == 3)
.peek(s -> System.out.println("after takeWhile: "+s))
.forEach(System.out::println);
将会打印
before takeWhile: foo
after takeWhile: foo
foo
before takeWhile: bar
after takeWhile: bar
bar
before takeWhile: baz
after takeWhile: baz
baz
before takeWhile: hello
这说明它不会在必要时处理超过所需的元素。在 takeWhile
阶段之前,我们必须遇到第一个不匹配的元素,然后,我们只会遇到那些元素。
takeWhile
? - NamantakeWhile()
函数。 - Samuel PhilippValidationResult
中有哪些值?是否可以忽略其字段,只关心isError
和剩下的验证器?如果是这样,请查看我的答案... - buræquetetakeWhile
不起作用,因为问题中包括“包括”一词。 - Eugene