Java Stream takeWhile and dropWhile

Introduction

Before Java 9, there was no way to pick or drop a subset/subsequence of a stream based on a condition. As part of Java 9, the Stream interface has two methods takeWhile and dropWhile. This post will explore the Java Stream takeWhile and dropWhile methods.

Use case of taking or dropping a sequence of stream

Take while

A takeWhile operation is very useful for an infinite stream. We want to keep processing the elements of an infinite steam until a condition is satisfied. When we encounter an element which fails to satisfy a condition, we want to stop processing. This can also be extended to finite streams as well.

Drop while

We want to drop all elements till we encounter an element that satisfies the predicate. From that element onwards, all the remaining elements in the stream should be processed.

Difference between an Ordered and an Unordered Stream

Knowing the difference between an ordered and unordered stream is important before getting into the takeWhile and dropWhile methods as they operate differently for them. 

An ordered stream is one that is created out of a collection that produces a stream with encounter order – example, List and array. Since a list or an array is ordered, the stream created out of them is ordered.

For other collection sources like a HashSet or a HashMap, which doesn’t have a guaranteed order, the stream created with them as the source results in an unordered stream.

The takeWhile method

From the javadoc, 

For an ordered stream:
It will return a stream that has the longest prefix of elements from the stream that match the given predicate i.e., when it sees an element failing the predicate the remaining elements in the stream (including the failing element) will be dropped.
 
For an unordered stream:
It returns a stream which has a subset of elements from the stream that match the predicate. Because there is no defined encounter order, the elements in the stream can be processed in any order by the predicate of the takeWhile operation. For example, if the first element it encounters does not match the condition, it will not pick any element and results in an empty stream.
 
This is a short-circuiting intermediate operation.
 

The dropWhile method

For an ordered stream:
Drops a longest prefix of elements from the stream that match the predicate and returns the remaining elements.
In other words, it will drop all the elements till it sees an element for which the predicate evaluation fails. It will return that element along with all the elements to follow it in the stream.
 
For an unordered stream:
It returns a stream after dropping a subset of elements from the stream that matches the predicate. What elements are returned depends on what order it encounters the elements during a run (since the stream in unordered). For example, if the first element it encounters does not match the condition, it will return all the elements in the stream without dropping anything.
 
This is a short-circuiting intermediate operation.

Take While on an ordered stream

Infinite stream

The takeWhile is very useful to break out of an infinite stream when a condition matches. Let us create an infinite stream by using Stream.iterate method by emitting integers and pick the elements which are less than 6.
The Seven Ways to Create a Stream in Java shows various ways to create a Stream in Java. 

Stream.iterate(0, i -> i + 1)
        .takeWhile(i -> i <= 5)
        .forEach(System.out::println);

This prints,

0
1
2
3
4
5

When 6 is processed, it fails to satisfy the predicate and hence the stream is short-circuited.

Finite stream

The same can be applied to a finite stream. Here we have a stream of strings and process till the predicate matches the element.

List<String> list = List.of("a", "b", "c", "d", "e");
list.stream()
        .takeWhile(element -> !element.equals("d"))
        .forEach(System.out::println);

We quit when we see a ‘d’. This prints,

a
b
c

When no elements match

When the first processed element fails to match the condition, it stops the processing and results in an empty stream. For the same list, we change the condition to take as long as the element (string) is equal to ‘f’. The below code does not print anything (as the first element processed ‘a’ fails to match the predicate).

//no match
list.stream()
        .takeWhile(element -> element.equals("f"))
        .forEach(System.out::println);

When all elements match

When the passed predicate evaluates to true for all the elements in the stream, then all the elements will be picked up.

//all match
list.stream()
        .takeWhile(element -> !element.equals("f"))
        .forEach(System.out::println);
a
b
c
d
e

Take While on an unordered stream

Infinite stream

Stream#generate creates an infinite unordered stream. We take while the elements match some condition. The below example uses an atomic integer to generate a sequence of integers and we pick as long as the element is less than 6. Thus it prints integers 1 to 5.

AtomicInteger atomicInteger = new AtomicInteger(0);
Stream.generate(atomicInteger::incrementAndGet)
        .takeWhile(i -> i <= 5)
        .forEach(System.out::println);

Finite stream

We use the collection factory method to create a Set. The Set created out of it can result in different iteration order for each run. Hence, the order in which the elements are processed will affect which subset of elements that are picked (which might even include the empty set).

Set<String> set = Set.of("ab", "cd", "af");
set.stream()
        .takeWhile(element -> element.startsWith("a"))
        .forEach(System.out::println);

If ‘cd’ is the first element in the stream, then the result will be an empty stream. Otherwise it could pick any of the following – ab, af / af, ab / ab / af (depends on where the string ‘cd’ is in the stream).

When no elements match

In case when none of the elements match, the predicate will fail for the first element in the stream and hence will result in an empty stream.

//no match
set.stream()
        .takeWhile(element -> element.startsWith("f"))
        .forEach(System.out::println);

When all elements match

It picks all elements since all the elements match the condition passed.

//all match
set.stream()
        .takeWhile(element -> element.length() == 2)
        .forEach(System.out::println);

Drop While on an ordered stream

Infinite stream

Using dropWhile on an infinite stream will still result in an infinite stream. The below code generates an infinite ordered stream using Stream.iterate. The elements from 1 to 5 will be dropped and elements from 6 onwards will be selected.

Stream.iterate(0, i -> i + 1)
         .dropWhile(i -> i <= 5)
         .forEach(System.out::println); //infinite stream!!

Finite stream

In the finite stream created we drop till the element is not ‘c’. Hence, this results in a stream with elements c, d, e.

List<String> list = List.of("a", "b", "c", "d", "e");
list.stream()
        .dropWhile(element -> !element.equals("c"))
        .forEach(System.out::println);

When no elements match

When none of the elements match the predicate, it results in a stream same as the original. This is because the first element processed will be evaluated false and all elements (including it) will be picked.

//no match
list.stream()
        .dropWhile(element -> element.equals("f"))
        .forEach(System.out::println);

Prints,

a
b
c
d
e

When all elements match

Contrary to the above case, here all the elements match the condition and there isn’t an element for which the predicate fails and hence it will drop all elements which results in an empty stream.

//all match 
list.stream()
        .dropWhile(element -> !element.equals("f"))
        .forEach(System.out::println);

Drop While on an unordered stream

Finite stream

We use the same Set.of to create a finite unordered stream. 

Set<String> set = Set.of("ab", "cd", "af");

set.stream()
         .dropWhile(element -> element.startsWith("a"))
         .forEach(System.out::println);
  • If the stream is ordered ab -> cd -> af
    • Then, the result is cd and af
  • If the stream is ordered ab -> af -> cd
    • Then, the result is cd
  • The stream is ordered cd -> af -> ab (the first element does not match and hence it picks all)
    • Then, the result is cd, af and ab
In short, based on the order, many combinations of outputs are possible. Note: Empty stream is not possible.

When no elements match

This results in a stream with all elements.

//no match
set.stream()
        .dropWhile(element -> element.startsWith("f"))
        .forEach(System.out::println);

When all elements match

This will result in an empty stream as it keeps dropping each of the elements.

//all match 
set.stream()
        .dropWhile(element -> element.length() == 2)
        .forEach(System.out::println);

Take While and Drop While on parallel streams

The takeWhile and dropWhile can have a bad performance in parallel pipelines with ordered streams because they will have to return or drop a valid prefix of elements. If ordering is not required, you can call unordered() on the stream to remove the ordering constraints. If ordering is required, you will have to switch to a sequential stream with sequential().

Comparing with other similar Stream methods

Let us compare the takeWhile and dropWhile with other similar Stream methods.

filter

Streams have a filter operation that filters out the elements that do not match the predicate. But it processes the entire stream. On the other hand, takeWhile and dropWhile take or drop a contiguous or sequence of elements.

In other words, for takeWhile, there many be elements that will satisfy the predicate in the discarded remaining portion of the stream. Similarly, for dropWhile, there may be elements that fail to satisfy the condition in the selected portion of the stream.

Stream skip and limit

The Stream#skip and Stream#limit methods are closer to the Stream#dropWhile and Stream#takeWhile methods. But they both take the number of elements to be skipped(dropped) or to be picked, respectively.

skip returns a stream after discarding first n elements of the stream whereas dropWhile drops an initial sequence of elements that match a given condition.
stream limit operation returns a stream that has the first n elements whereas takeWhile returns a prefix of elements from the stream that satisfies a given condition.

Conclusion

This concludes this post on the Java Stream takeWhile and dropWhile methods. We looked into these two methods that enables us to return or drop a prefix of elements in an ordered stream and any subset of elements in an unordered stream based on a passed predicate.

References and Resources

Leave a Reply