Limit a stream by a predicate

2018-12-31 05:12发布

Is there a Java 8 stream operation that limits a (potentially infinite) Stream until the first element fails to match a predicate?

In Java 9 we can use takeWhile as in the example below to print all the numbers less than 10.

IntStream
    .iterate(1, n -> n + 1)
    .takeWhile(n -> n < 10)
    .forEach(System.out::println);

As there is no such operation in Java 8, what's the best way of implementing it in a general way?

18条回答
忆尘夕之涩
2楼-- · 2018-12-31 05:44

Go to get library AbacusUtil. It provides the exact API you want and more:

IntStream.iterate(1, n -> n + 1).takeWhile(n -> n < 10).forEach(System.out::println);

Declaration: I'm the developer of AbacusUtil.

查看更多
不再属于我。
3楼-- · 2018-12-31 05:46

Actually there are 2 ways to do it in Java 8 without any extra libraries or using Java 9.

If you want to print numbers from 2 to 20 on the console you can do this:

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);

The output is in both cases:

2
4
6
8
10
12
14
16
18
20

No one mentioned anyMatch yet. This is the reason for this post.

查看更多
孤独寂梦人
4楼-- · 2018-12-31 05:49

You can't abort a stream except by a short-circuiting terminal operation, which would leave some stream values unprocessed regardless of their value. But if you just want to avoid operations on a stream you can add a transform and filter to the stream:

import java.util.Objects;

class ThingProcessor
{
    static Thing returnNullOnCondition(Thing thing)
    {    return( (*** is condition met ***)? null : thing);    }

    void processThings(Collection<Thing> thingsCollection)
    {
        thingsCollection.stream()
        *** regular stream processing ***
        .map(ThingProcessor::returnNullOnCondition)
        .filter(Objects::nonNull)
        *** continue stream processing ***
    }
} // class ThingProcessor

That transforms the stream of things to nulls when the things meet some condition, then filters out nulls. If you're willing to indulge in side effects, you could set the condition value to true once some thing is encountered, so all subsequent things are filtered out regardless of their value. But even if not you can save a lot of (if not quite all) processing by filtering values out of the stream that you don't want to process.

查看更多
忆尘夕之涩
5楼-- · 2018-12-31 05:50

If you have different problem, different solution may be needed but for your current problem, I would simply go with:

IntStream
    .iterate(1, n -> n + 1)
    .limit(10)
    .forEach(System.out::println);
查看更多
春风洒进眼中
6楼-- · 2018-12-31 05:54

takeWhile is one of the functions provided by the protonpack library.

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));
查看更多
荒废的爱情
7楼-- · 2018-12-31 05:54

I have another quick solution by implementing this (which is rly unclean in fact, but you get the idea):

public static void main(String[] args) {
    System.out.println(StreamUtil.iterate(1, o -> o + 1).terminateOn(15)
            .map(o -> o.toString()).collect(Collectors.joining(", ")));
}

static interface TerminatedStream<T> {
    Stream<T> terminateOn(T e);
}

static class StreamUtil {
    static <T> TerminatedStream<T> iterate(T seed, UnaryOperator<T> op) {
        return new TerminatedStream<T>() {
            public Stream<T> terminateOn(T e) {
                Builder<T> builder = Stream.<T> builder().add(seed);
                T current = seed;
                while (!current.equals(e)) {
                    current = op.apply(current);
                    builder.add(current);
                }
                return builder.build();
            }
        };
    }
}
查看更多
登录 后发表回答