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!

1. dropWhile and takeWhile

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 takeWhile and dropWhile methods:

takeWhile and dropWhile are complementary to each other. They’re basically versions of limit and skip which take a predicate instead of a number.

2. iterate

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 for.

3. ofNullable

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 null though:

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!

4. Collectors.filtering and Collectors.flatMapping

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, filtering and flatMapping, which play nice with the groupingBy collector.

Collectors.filtering

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

Collectors.flatMapping is to Collectors.mapping what 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.mapping, 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.mapping, however, 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!