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,
The dropWhile method
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
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.