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
andaf
- Then, the result is
- If the stream is ordered
ab
->af
->cd
- Then, the result is
cd
- Then, the result is
- The stream is ordered
cd
->af
->ab
(the first element does not match and hence it picks all)- Then, the result is
cd
,af
andab
- Then, the result is
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.
A 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.
A 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.