是否有Java 8流操作限制流(可能是无限的),直到第一个元素无法匹配谓词?
在Java 9中,我们可以像下面的例子一样使用takeWhile来打印所有小于10的数字。
IntStream
.iterate(1, n -> n + 1)
.takeWhile(n -> n < 10)
.forEach(System.out::println);
因为在Java 8中没有这样的操作,那么以通用的方式实现它的最佳方法是什么呢?
是否有Java 8流操作限制流(可能是无限的),直到第一个元素无法匹配谓词?
在Java 9中,我们可以像下面的例子一样使用takeWhile来打印所有小于10的数字。
IntStream
.iterate(1, n -> n + 1)
.takeWhile(n -> n < 10)
.forEach(System.out::println);
因为在Java 8中没有这样的操作,那么以通用的方式实现它的最佳方法是什么呢?
当前回答
这是在int上做的一个版本-正如问题中所问的那样。
用法:
StreamUtil.takeWhile(IntStream.iterate(1, n -> n + 1), n -> n < 10);
下面是StreamUtil的代码:
import java.util.PrimitiveIterator;
import java.util.Spliterators;
import java.util.function.IntConsumer;
import java.util.function.IntPredicate;
import java.util.stream.IntStream;
import java.util.stream.StreamSupport;
public class StreamUtil
{
public static IntStream takeWhile(IntStream stream, IntPredicate predicate)
{
return StreamSupport.intStream(new PredicateIntSpliterator(stream, predicate), false);
}
private static class PredicateIntSpliterator extends Spliterators.AbstractIntSpliterator
{
private final PrimitiveIterator.OfInt iterator;
private final IntPredicate predicate;
public PredicateIntSpliterator(IntStream stream, IntPredicate predicate)
{
super(Long.MAX_VALUE, IMMUTABLE);
this.iterator = stream.iterator();
this.predicate = predicate;
}
@Override
public boolean tryAdvance(IntConsumer action)
{
if (iterator.hasNext()) {
int value = iterator.nextInt();
if (predicate.test(value)) {
action.accept(value);
return true;
}
}
return false;
}
}
}
其他回答
这样的操作在Java 8 Stream中应该是可能的,但它不一定能高效地完成——例如,您不一定能将这样的操作并行化,因为您必须按顺序查看元素。
API并没有提供简单的方法,但最简单的方法可能是获取Stream. Iterator(),将迭代器包装为“take-while”实现,然后返回到Spliterator,然后是Stream。或者——可能——包装Spliterator,尽管在这个实现中它不能再被拆分了。
下面是一个未测试的takeWhile在Spliterator上的实现:
static <T> Spliterator<T> takeWhile(
Spliterator<T> splitr, Predicate<? super T> predicate) {
return new Spliterators.AbstractSpliterator<T>(splitr.estimateSize(), 0) {
boolean stillGoing = true;
@Override public boolean tryAdvance(Consumer<? super T> consumer) {
if (stillGoing) {
boolean hadNext = splitr.tryAdvance(elem -> {
if (predicate.test(elem)) {
consumer.accept(elem);
} else {
stillGoing = false;
}
});
return hadNext && stillGoing;
}
return false;
}
};
}
static <T> Stream<T> takeWhile(Stream<T> stream, Predicate<? super T> predicate) {
return StreamSupport.stream(takeWhile(stream.spliterator(), predicate), false);
}
实际上,在Java 8中有两种方法可以做到这一点,不需要任何额外的库或使用Java 9。
如果你想在控制台上打印2到20的数字,你可以这样做:
IntStream.iterate(2, (i) -> i + 2).peek(System.out::println).allMatch(i -> i < 20);
or
IntStream.iterate(2, (i) -> i + 2).peek(System.out::println).anyMatch(i -> i >= 20);
两种情况下的输出都是:
2
4
6
8
10
12
14
16
18
20
还没有人提到match。这就是我写这篇文章的原因。
如果你有不同的问题,可能需要不同的解决方案,但对于你当前的问题,我只想说:
IntStream
.iterate(1, n -> n + 1)
.limit(10)
.forEach(System.out::println);
allMatch()是一个短路函数,因此可以使用它来停止处理。主要的缺点是您必须进行两次测试:一次是查看是否应该处理它,另一次是查看是否继续进行。
IntStream
.iterate(1, n -> n + 1)
.peek(n->{if (n<10) System.out.println(n);})
.allMatch(n->n < 10);
作为@StuartMarks回答的后续。我的StreamEx库具有takeWhile操作,该操作与当前JDK-9实现兼容。当在JDK-9下运行时,它只会委托给JDK实现(通过MethodHandle。invokeExact非常快)。在JDK-8下运行时,将使用“polyfill”实现。所以使用我的库可以像这样解决问题:
IntStreamEx.iterate(1, n -> n + 1)
.takeWhile(n -> n < 10)
.forEach(System.out::println);