In the last post in this series, we took a look at the new convenience factory methods for collections in Java 9. Collections play well with the Stream API, which learned a few new tricks since its introduction in Java 8 as well. There are 4 particularly exciting new features – let’s explore them with JShell!
Let’s take a step back and look at what was available prior to Java 9. We had an API for making streams containing elements. For example, let’s create a stream of numbers 0 to 9:
Since Java 8, we’ve known
limit(long n) and its complementary method
skip(long n), which allow us to take and ignore the first
n elements of a stream, respectively. For instance:
These methods are very useful. However, they require us to know the number of elements to limit or skip in advance. Often, we know we want to keep taking or dropping elements while a certain condition is met, but we don’t necessarily know how many of these element we get. That’s exactly the functionality added in Java 9, courtesy of
dropWhile are complementary to each other. They’re basically versions of
skip which take a predicate instead of a number.
Another useful addition to the Stream API is the method
iterate. The method is overloaded:
The version on line 3 was available in JDK 8, whereas the version on line 4 was added in JDK 9. To illustrate the difference, let’s try and solve a simple problem – suppose we want to print all even numbers less than 10.
A naive approach to solving this problem using Java 8 constructs could involve iteration with a filtering step, such as:
As we can see, if we try to run this simple and seemingly straightforward code, we discover it doesn’t work. At first, the correct solution is printed, then the output pauses, and continues to print incorrect numbers after a while.
It’s easy to see what’s happening here. We construct the right filter, but apply it to an infinite stream. After printing the elements matching the filtering condition, the stream silently keeps going. Our integers overflow and start matching the filtering condition again.
Unfortunately, there isn’t a concise and clean way to deal with this situation in JDK 8, which is what the recent release of the JDK fixes. We can now correctly solve our problem as follows:
If we look at the solution, we can see this version of
iterate reads like a
for loop, and that’s essentially what it is – a streamified version of
Another addition to the Stream API worth talking about is the method
ofNullable. Prior to Java 9, we could create streams of arbitrary elements using the factory methods. For example:
The result is a stream containing a single element –
1. We couldn’t do the same thing with
This makes sense – we don’t want to have
nulls in our streams. Java 9, however, allows us to do the following:
The result is not a stream containing a
null element, but an empty stream:
If you’ve worked with streams a lot, you probably immediately realize why this is an exciting feature. It makes integration better, and allows us to construct cleaner streams. Often, when using long streams with many chained operations, particularly
maps, we end up with
nulls at some point in our stream. Since we don’t want to have
nulls in streams, or
NullPointerExceptions, we need to filter the
null elements out. In practice, this involves a step with a ternary operator or an
if statement, none of which are nice.
ofNullable enables us to avoid these
null checks, which is great!
Collectors are an essential part of the Stream API. They provide reduction operations which process stream elements into result containers, optionally performing a transformation on the aggregated results, after all input elements have been processed.
Java 9 added 2 new collectors,
flatMapping, which play nice with the
Similarly to the
filter method on streams, the
filtering collector is used for filtering elements in a stream. The
filter method, however, processes the values before they’re grouped, whereas
Collectors.filtering can be used nicely with
Collectors.groupingBy to group the values before the filtering step takes place.
Let’s demonstrate the difference on an example. Suppose we’re given a list of numbers, e.g.:
Now suppose we need to count the numbers by parity, but we’re only interested in the numbers greater than 5. Prior to Java 9, we would accomplish this as follows:
The result correctly says that we have 3 odd numbers greater than 5 in our list. However, it doesn’t tell us we don’t have any even numbers there. The even numbers were filtered out before aggregation, and any trace of them is lost. That’s exactly what the
filtering collector addresses. In Java 9, we can count the numbers in both bags as follows:
As we can see now, there are clearly no even and 3 odd numbers matching our condition.
Collectors.flatMapping is to
Stream.flatmap is to
Stream.map. Like the
flatmap operation, the
flatMapping collector allows us to handle nested collections (collections in streams) better, in this case on the collectors’ side. Like
Collectors.flatMapping takes a function to be applied to the input elements, and a collector to accumulate the elements passed through the function. Unlike
Collectors.flatMapping deals with a stream of elements, which allows us to get rid of often unnecessary intermediary collections.
Consider a stream of some data entries, for example integers associated with collections of strings, such as the following:
Now suppose we want to aggregate this data, e.g. group the strings by the integers they’re associated with. Using Java 8 constructs, we would accomplish this as follows:
Although this is technically correct, we end up with nested collections, which we would need to further unwrap to, for example, deal with duplicates. Fortunately, that’s exactly what the new
flatMapping collector is for:
To sum up, the updates to the Stream API introduced in Java 9 are not huge. They do, however, tie up some loose ends, and ultimately allow us to write cleaner code, which is always exciting!