Java 9 Improvements

RunninglVlan
31.8K views

Open Source Your Knowledge, Become a Contributor

Technology knowledge has to be shared and made accessible for free. Join the movement.

Create Content
Next: Collectors Improvements

We've found, while teaching developers about lambdas and Streams in Java 8, that many developers really appreciate the code that comes out at the other end. It tends to read a lot more like the problem that developers are trying to solve than traditional for loops and, as a side effect, is often shorter to boot. That doesn't mean that the Streams API is perfect or even complete. In this article we will be looking at a few small improvements that were introduced in Java 9 that make it even better.

ofNullable

The Stream interface has a couple of factory methods called of() that allow you to create Streams from pre-specified values - one is an overload for a single value and the other takes a varargs parameter. These are incredibly useful both when you're trying to test out Streams code and also when you want to just instantiate a Stream with a few values. Java 9 adds an ofNullable() factory - let's see how you might use it and what it's for.

Let's suppose we're trying to find a location to put some configuration files in a Java application. We want to look at a couple of different properties - e.g. "app.config" and "app.home". Let's write this code in Java 8:

final String configurationDirectory =
    Stream.of("app.config", "app.home", "user.home")
        .flatMap(key -> {
            final String property = System.getProperty(key);
            if (property == null) {
                return Stream.empty();
            }
            else {
                return Stream.of(property);
            }
        })
        .findFirst()
        .orElseThrow(IllegalStateException::new);

Hmm, that's a little bit ugly right - What's going on here? Well we're looking up each property in our Stream and using the flatMap operation. We use flatMap here because it allows us to map an element to 0 or 1 elements in the Stream. If we can lookup the system property then we return a Stream containing only it, but if we can't look it up then we return an empty Stream in its place. This results in no element being added into the stream.

Unfortunately what we've ended up with is a fairly large statement style lambda expression with a null check in the middle of the code. One alternative would be to use a ternary operator

final String configurationDirectory =
    Stream.of("app.config", "app.home", "user.home")
        .flatMap(key -> {
            final String property = System.getProperty(key);
            return property == null ? Stream.empty() : Stream.of(property);
        })
        .findFirst()
        .orElseThrow(IllegalStateException::new);

Even after this refactor, however, the code reads slightly inelegantly. Java 9's ofNullable would allow us to write the same pattern much more succinctly and more readably.

It's worth noting for the sake of completeness that this isn't the only way to solve this problem in code, we could also have mapped over the System.getProperty() function and then filtered out the null values. That is perhaps a more natural way of writing this code for some people, but has the downside of resulting in null values getting into our Stream - something we try to steer clear of.

takeWhile and dropWhile

We have an application that is processing payments being made on an ecommerce website and we're maintaining a list of all payments in the current day that are sorted from the most expensive down to the cheapest. We have a business requirement to produce a report on every payment that is £500 or greater in value at the end of the day. A natural way of writing this code using Java 8 Streams might be:

final List<Payment> expensivePayments = paymentsByValue
    .stream()
    .filter(payment -> payment.getValue() >= 500)
    .collect(toList());

Unfortunately the downside of this approach is that if you start processing lots and lots of transactions in a day the filter operation gets applied to every transaction in your input list. You know that your input list is sorted by descending value of the transaction, so once you have found a transaction that fails your predicate every transaction after that point can be filtered out. Thankfully Java 9 solves this problem with the addition of a takeWhile operation.

While filter retains all elements in the Stream that match its predicate, takeWhile stops once it has found an element that fails to match. The dropWhile operation does the inverse: throws away the elements at the start where the predicate is false.

One subtlety that affects takeWhile() and dropWhile() relates to infinite streams. If you have an infinite stream and apply a takeWhile operation that eventually returns false on an element in the stream then it gets truncated at that point. When applying the dropWhile operation to an infinite stream, the result can still be an infinite stream. If the the predicate always returns true then the Stream will continue to drop elements. Consider:

IntStream.iterate(0, i -> i)
    .dropWhile(i -> true)
    .forEach(System.out::println);

If you run this, the program will look like it's hanging and not terminate. This is because the dropWhile results in an infinite stream with no elements. If we re-wrote it as the following program, then it would terminate:

IntStream.iterate(0, i -> i)
    .takeWhile(i -> false)
    .forEach(System.out::println);

Here's a visual example:

Now so far we've talked about streams that have a defined order: an encounter order. The order of streams can be defined at its source, for example if we're streaming from a list of values then the order in the list is the encounter order. It is also possible to have stream operations that introduce an encounter order into their pipeline, for example sorted(). Most, but not all, of the practical use cases of takeWhile() and dropWhile() rely upon their input streams having a defined encounter order.

One usecase for wanting to apply takeWhile() on an unordered stream if you want to be able to stop the Stream operation. For example perhaps you have a Stream operation that may operate on an infinite stream, processing all the data in it, but you want to be able to stop the Stream when you application shuts down or if a user needs to cancel the stream pipeline. You can do this with a takeWhile() operation that reads from a piece of external state, such as a volatile boolean flag. When you want to stop the stream pipeline, you simply set it to be false.

iterate

A related update is the introduction of an alternative iterate() method for creating streams. The vintage iterate method from Java 8 takes an initial value and a function that provides the next value in the Stream. Take a look at the following code example.

IntStream.iterate(3, x -> x + 3)
     .filter(x -> x < 16)
     .forEach(System.out::println);

This code prints out all the numbers that are divisible by 3 and less than 16. We start with 3, which is divisible by 3 and add 3 every time we iterate. We then filter to ensure that the numbers are less than 16 and use a method reference to print out the resulting numbers. Looks simple, but if you run it you'll find that there's a pretty big problem.

The program never terminates, it keeps on adding 3 in a loop indefinitely. That's because there's no way to know in the filter that the numbers continue to increase. We can solve that problem with the new version of iterate in Java 9 which also takes a predicate as its second argument that tells us when to continue iterating up until. So we rewrite our code as follows.

Hey presto, it now stops running after it has printed out the number 15. Our sample code here used the IntStream interface since we were operating on primitive int values, but the iterate() methods appear on both the primitive and regular Stream interfaces.

Conclusion

In this article we've gone through four new additions to the Streams API that appear in Java 9 and help fill in some small gaps in its functionality. In the next article we will be talking about the improvements to the Collectors API in Java 9. Hopefully you will join us for that, or on one of our in-house training courses.

Notes

This playground is based on IteratrLearning's article Stream Improvements in Java 9

Similar playground by Gowgi: Java 9 Streams Enhancements

Open Source Your Knowledge: become a Contributor and help others learn. Create New Content