How do you reverse order a Stream in Java? There’s no obvious Stream.reverse() method. Is this an oversight in the API? Is there a workaround?
It’s not an oversight. It simply not possible to reverse order a Stream because of how it works. A Stream is not a data structure. It’s an API to perform operations on a data structure. It’s a bit like an Iterator but with richer functionality. In fact, many Stream operations are built on top of the Iterator and Spliterator interfaces. These allow you to traverse a source (usually a Collection) sequentially starting from the beginning. There’s no way to access elements randomly like List.get() and there’s no way to iterate from the end like Deque.getLast(). This allows no mechanism to reverse order a Stream unless we collect it to an intermediate data structure.
There’s also the question how how you’d reverse the order of an infinite length Stream such as Random.ints() or the result of Stream.iterate(). If you were to reverse an infinite length stream, what would be the first element?
There are however some ways to work around this. Be aware though that they all require reading the entire Stream to an intermediate data structure so you’ll lose some of the performance and lazy evaluation benefits of Streams.
Reverse after after collect()
The simplest way to get round this is to collect() the Stream to a data structure (for example, a List) and then reverse the collected data structure.
As an example, here’s a method that takes a String of HTML and returns a Stream of HTML tag names:
private static Stream<String> tagsStream(String tags) {
Matcher tagMatcher = Pattern.compile("<(\\w+).*?\>").matcher(tags);
return tagMatcher.results()
.map(result -> result.group(1));
}
We want to use the resulting Stream to generate the corresponding closing tags. This involves:
- Wrapping each tag name with </close tag> angle brackets;
- Reversing the order of tags
The steps can be performed in any order.
We can create a List of closing tags collecting the Stream to a List and then reversing the result:
public static String closeTagsReverseAfterCollect(String openTags) {
List<String> closeTags = tagsStream(openTags)
.map(tag -> "</" + tag + ">")
.collect(Collectors.toList());
Collections.reverse(closeTags);
return String.join("", closeTags);
}
Use an intermediate data structure
A more elegant solution is to write a custom Collector that returns a reversed list. It’s fairly simple and leverages the existing Collectors.toList() Collector:
private static <T> Collector<T, ?, List<T>> toReversedList() {
return Collectors.collectingAndThen(Collectors.toList(), list -> {
Collections.reverse(list);
return list;
});
}
This simplifies our Stream based method to close HTML tags to look like this:
public static String closeTagsReversedListCollector(String openTags) {
List<String> closeTags = tagsStream(openTags)
.map(tag -> "</" + tag + ">")
.collect(toReversedList());
return String.join("", closeTags);
}
The result of the collect() is a List in reverse order – no further manipulation required.
Reverse order on collect()
Finally, if you absolutely must reverse the order of the Stream so that you can perform further operations on it, a Collector is no use. collect() is a terminating operation so you can’t perform further Stream operations unless you create a new Stream from the resulting List.
A static method to reverse a Stream requires an intermediate data structure. I’ve used a LinkedList (implements Deque) as it allows us to easily create a reversed data structure by iteratively push()ing each element to the head of the list. The last element push()ed will be the first element of the list.
private static <T> Stream<T> reverse(Stream<T> stream) {
LinkedList<T> stack = new LinkedList<>();
stream.forEach(stack::push);
return stack.stream();
}
To demonstrate, in this example we reverse the order of tags names and then create closing tags as a subsequent Stream operation.
public static String closeTagsReverseStream(String openTags) {
List<String> closeTags = reverse(tagsStream(openTags))
.map(tag -> "</" + tag + ">")
.collect(Collectors.toList());
return String.join("", closeTags);
}
Be First to Comment