是否有一种Java 8的流操作可以将一个(可能是无限的)Stream
限制到第一个不符合谓词的元素?
在Java 9中,我们可以像下面的示例一样使用takeWhile
来打印所有小于10的数字。
IntStream
.iterate(1, n -> n + 1)
.takeWhile(n -> n < 10)
.forEach(System.out::println);
在Java 8中没有这样的操作,最好的一般实现方式是什么?
是否有一种Java 8的流操作可以将一个(可能是无限的)Stream
限制到第一个不符合谓词的元素?
在Java 9中,我们可以像下面的示例一样使用takeWhile
来打印所有小于10的数字。
IntStream
.iterate(1, n -> n + 1)
.takeWhile(n -> n < 10)
.forEach(System.out::println);
在Java 8中没有这样的操作,最好的一般实现方式是什么?
JDK 9增加了takeWhile
和dropWhile
操作。你的示例代码:
IntStream
.iterate(1, n -> n + 1)
.takeWhile(n -> n < 10)
.forEach(System.out::println);
当在JDK 9下编译和运行时,代码将会按你预期的方式执行。
JDK 9已经发布。可以在此处下载:JDK 9发布版。
takeWhile
/dropWhile
йў„и§Ҳж–ҮжЎЈзҡ„зӣҙжҺҘй“ҫжҺҘпјҡhttp://download.java.net/jdk9/docs/api/java/util/stream/Stream.html - MilestakeWhile
和dropWhile
在Scala、Python、Groovy、Ruby、Haskell和Clojure中非常普遍。与skip
和limit
的不对称性很不幸。也许本应该将skip
和limit
称为drop
和take
,但除非您已经熟悉Haskell,否则这些名称并不那么直观易懂。 - Stuart MarksdropXXX
和 takeXXX
更为流行,但个人认为类似 SQL 的 limitXXX
和 skipXXX
也可以接受。我发现这种新的不对称性比术语的个人选择更加令人困惑... :) (顺便说一下:Scala 也有 drop(int)
和 take(int)
) - Lukas EderIntStream .iterate(1, n -> n + 1) .takeWhile(n -> n < 10)
can be simplified to IntStream .iterate(1, n -> n < 10, n -> n + 1)
- Holger使用 Java 8 Stream
可能可以完成这样的操作,但不能保证效率——例如,您不能一定将这样的操作并行化,因为您必须按顺序查看元素。
API 没有提供一种简单的方法来实现它,但可能最简单的方法是使用 Stream.iterator()
,对 Iterator
进行“take-while”实现的包装,然后返回到 Spliterator
,最后返回到 Stream
。 或者——也许——包装 Spliterator
,尽管在此实现中它不再能够被分割。
以下是未经测试的在 Spliterator
上实现 takeWhile
的内容:
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);
}
allMatch()
是一种短路函数,因此您可以使用它来停止处理。主要的缺点是你需要测试两次:一次用于确定是否应该处理它,另一次用于确定是否继续。
IntStream
.iterate(1, n -> n + 1)
.peek(n->{if (n<10) System.out.println(n);})
.allMatch(n->n < 10);
takeWhile
操作。在JDK-9下运行时,它会通过MethodHandle.invokeExact
(非常快)委托给JDK实现。在JDK-8下运行时,将使用“polyfill”实现。因此,使用我的库可以这样解决问题:IntStreamEx.iterate(1, n -> n + 1)
.takeWhile(n -> n < 10)
.forEach(System.out::println);
takeWhile
是protonpack库提供的函数之一。
Stream<Integer> infiniteInts = Stream.iterate(0, i -> i + 1);
Stream<Integer> finiteInts = StreamUtils.takeWhile(infiniteInts, i -> i < 10);
assertThat(finiteInts.collect(Collectors.toList()),
hasSize(10));
更新:Java 9 Stream
现在配备了一个takeWhile 方法。
不需要使用任何hack或其他解决方案,只需使用它!
我相信这可以大大改善: (也许有人可以使它线程安全)
Stream<Integer> stream = Stream.iterate(0, n -> n + 1);
TakeWhile.stream(stream, n -> n < 10000)
.forEach(n -> System.out.print((n == 0 ? "" + n : "," + n)));
class TakeWhile<T> implements Iterator<T> {
private final Iterator<T> iterator;
private final Predicate<T> predicate;
private volatile T next;
private volatile boolean keepGoing = true;
public TakeWhile(Stream<T> s, Predicate<T> p) {
this.iterator = s.iterator();
this.predicate = p;
}
@Override
public boolean hasNext() {
if (!keepGoing) {
return false;
}
if (next != null) {
return true;
}
if (iterator.hasNext()) {
next = iterator.next();
keepGoing = predicate.test(next);
if (!keepGoing) {
next = null;
}
}
return next != null;
}
@Override
public T next() {
if (next == null) {
if (!hasNext()) {
throw new NoSuchElementException("Sorry. Nothing for you.");
}
}
T temp = next;
next = null;
return temp;
}
public static <T> Stream<T> stream(Stream<T> s, Predicate<T> p) {
TakeWhile tw = new TakeWhile(s, p);
Spliterator split = Spliterators.spliterator(tw, Integer.MAX_VALUE, Spliterator.ORDERED);
return StreamSupport.stream(split, false);
}
}
你可以使用Java8 + rxjava。
import java.util.stream.IntStream;
import rx.Observable;
// Example 1)
IntStream intStream = IntStream.iterate(1, n -> n + 1);
Observable.from(() -> intStream.iterator())
.takeWhile(n ->
{
System.out.println(n);
return n < 10;
}
).subscribe() ;
// Example 2
IntStream intStream = IntStream.iterate(1, n -> n + 1);
Observable.from(() -> intStream.iterator())
.takeWhile(n -> n < 10)
.forEach( n -> System.out.println(n));
实际上,在Java 8中有两种方法可以做到这一点,而不需要任何额外的库或使用Java 9。
如果你想在控制台上打印从2到20的数字,可以这样做:
IntStream.iterate(2, (i) -> i + 2).peek(System.out::println).allMatch(i -> i < 20);
或者
IntStream.iterate(2, (i) -> i + 2).peek(System.out::println).anyMatch(i -> i >= 20);
2
4
6
8
10
12
14
16
18
20
还没有人提到anyMatch。这就是这篇文章的原因。
这是从JDK 9的java.util.stream.Stream.takeWhile(Predicate)复制的源代码。为了兼容JDK 8,稍有不同。
static <T> Stream<T> takeWhile(Stream<T> stream, Predicate<? super T> p) {
class Taking extends Spliterators.AbstractSpliterator<T> implements Consumer<T> {
private static final int CANCEL_CHECK_COUNT = 63;
private final Spliterator<T> s;
private int count;
private T t;
private final AtomicBoolean cancel = new AtomicBoolean();
private boolean takeOrDrop = true;
Taking(Spliterator<T> s) {
super(s.estimateSize(), s.characteristics() & ~(Spliterator.SIZED | Spliterator.SUBSIZED));
this.s = s;
}
@Override
public boolean tryAdvance(Consumer<? super T> action) {
boolean test = true;
if (takeOrDrop && // If can take
(count != 0 || !cancel.get()) && // and if not cancelled
s.tryAdvance(this) && // and if advanced one element
(test = p.test(t))) { // and test on element passes
action.accept(t); // then accept element
return true;
} else {
// Taking is finished
takeOrDrop = false;
// Cancel all further traversal and splitting operations
// only if test of element failed (short-circuited)
if (!test)
cancel.set(true);
return false;
}
}
@Override
public Comparator<? super T> getComparator() {
return s.getComparator();
}
@Override
public void accept(T t) {
count = (count + 1) & CANCEL_CHECK_COUNT;
this.t = t;
}
@Override
public Spliterator<T> trySplit() {
return null;
}
}
return StreamSupport.stream(new Taking(stream.spliterator()), stream.isParallel()).onClose(stream::close);
}
以下是使用整数完成的版本 - 如问题所要求。
用法:
StreamUtil.takeWhile(IntStream.iterate(1, n -> n + 1), n -> n < 10);
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;
}
}
}
IntStream.iterate(1, n->n<10, n->n+1).forEach(System.out::print);
- Marc Dzaebel