Picking elements of a list until condition is met with Java 8 Lambdas

Using strictly just Java 8 API:

public static <R> Stream<? extends R> takeUntil(Iterator<R> iterator, Predicate<? super R> stopFilter) {
    final boolean isParallelStream = false;
    
    return StreamSupport.stream(Spliterators.spliteratorUnknownSize(new Iterator<R>() {
        private R next = null;
        private boolean conditionSatisfied = false;
        private boolean hasTaken = true;
        
        @Override
        public boolean hasNext() {
            if (conditionSatisfied || !iterator.hasNext()) {
                return false;
            }

            if (hasTaken) {
                next = iterator.next();
                conditionSatisfied = stopFilter.test(next);
                hasTaken = false;
            }
            return !conditionSatisfied;
        }

        @Override
        public R next() {
            if (!hasNext()) {
                throw new NoSuchElementException("There are no more items to consume");
            }
            hasTaken = true;
            return next;
        }
    }, 0), isParallelStream);
}

You can then specialize it in the following ways:

For streams

public static <R> Stream<? extends R> takeUntil(Stream<R> stream, Predicate<? super R> stopFilter) {
    return takeUntil(stream.iterator(), stopFilter);
}

For collections

public static <R> Stream<? extends R> takeUntil(Collection<R> col, Predicate<? super R> stopFilter) {
    return takeUntil(col.iterator(), stopFilter);
}

In JDK9 there will be a new Stream operation called takeWhile which does the thing similar to what you need. I backported this operation to my StreamEx library, so you can use it even in Java-8:

List<String> list = StreamEx.of(tokens)
                            .takeWhile(t -> !t.toUpperCase().endsWith("STOP"))
                            .toList();

Unfortunately it does not take the "STOP" element itself, so the second pass is necessary to add it manually:

list.add(StreamEx.of(tokens).findFirst(t -> t.toUpperCase().endsWith("STOP")).get());

Note that both takeWhile and findFirst are short-circuit operations (they will not process the whole input stream if unnecessary), so you can use them with very long or even infinite streams.

However using StreamEx you can solve it in single pass using the trick with groupRuns. The groupRuns method groups adjacent Stream elements to the List based on the supplied predicate which tells whether two given adjacent elements should be grouped or not. We may consider that the group ends with the element containing "STOP". Then we just need to take the first group:

List<String> list = StreamEx.of(tokens)
                            .groupRuns((a, b) -> !a.toUpperCase().endsWith("STOP"))
                            .findFirst().get();

This solution also will not do extra work when the first group is finished.


If you really must use Streams API, keep it simple and use a stream of indexes:

int lastIdx = IntStream.range(0, tokens.size())
        .filter(i -> tokens.get(i).toUpperCase().endsWith("STOP"))
        .findFirst()
        .orElse(-1);

List<String> myTokens = tokens.subList(0, lastIdx + 1);

Or make a new List out of the sublist if you want an independent copy that's not backed by the original list.