Optional patterns

Much has been said about Java 8 and about the way it will change the way we design applications and APIs. Of course the fisrt big thing is lambda expressions, followed by the Stream API.

Another element was introduced, the final class Optional, which also changes the way we can write API and applications, leading us to better and more fluent patterns. The purpose of this article is to detail the concept of optional, and show the available patterns to use optionals efficiently and elegantly. There are very powerful and very elegant way to mix optionals and streams, the aim of this article is to describe those patterns.

What is an Optional?

This concept is not particularly new, and already exists in other languages. This class was introduced to model the fact that a method may well not be able to return a value. In that case, the most common choice that is made is to return some kind of default value. For example, the call to map.get(key) returns null if the key that we pass as a parameter is not in the hash table. In fact, if the key is in the table associated with the value null, this method will also return null. So returning null is not the best solution, because it does not tell us if the key is present int he map or not.

It would be a mistake to think that the Optional is there only for that purpose: handle corner cases. In fact, we have been dealing with corner cases since Java was there, without optionals.  So we might wonder: do we really need optionals? But the way this class was designed authorizes new patterns which, combined with the patterns of the Stream API, become particularly simple.

Optionals are not an option

In fact this concept of “a result that does not exist” is required in many application cases. Let us see an example with the Stream API. Let us consider the simple calculation of a max. Since the Stream API exposes a max() method, taking a look at that point might prove interesting.

Let us write a Stream.max() call.

Stream<Integer> stream =
   Stream.of(1, 2, 3, 4) ;
stream.max(Comparator.naturalOrder()) ;

In this case, no worry, the result must be 4.

Let us consider another case, in which our stream is in fact empty. What is the value one should return?

We need to extend our example if we want to see the full problem. Suppose we have two streams, and let us write the following code.

Stream<Integer> stream1 =
   Stream.of(1, 2) ;
int max1 = stream1.max(Comparator.naturalOrder()) ;

Stream<Integer> stream2 =
   Stream.of(3, 4) ;
int max2 = stream2.max(Comparator.naturalOrder()) ;

Obviously the following property should hold:

int max = Integer.max(max1, max2) ;

Stream<Integer> stream3 =
   Stream.concat(stream1, stream2) ;
int max3 = stream3.max() ;

assert max == max3

If we merge two streams, the max of this stream should also be the max of the two max. We are doomed…

Can the max() method return null ? In fact the real question is the following: do we really want to have to handle null values in our data processing code? We do not know what to do with them, we will have to handle them as special cases, and to protect the rest of our code from them. Obviously the answer is in fact in the question: no.

Suppose that the max() method returns an int, or, in a more general way, the type on which the stream is built. What value should we choose in the case the stream on which we compute that max() is empty?

The first answer that will come to mind will probably be 0. Let us examine the following example.

int max1 =
   Stream.empty().max() ; // suppose max1 is 0
int max2 = 
   Stream.of(-1, -2, -3).max() ; // max2 is -1

int max = Integer.max(max1, max2) ; // thus 0

Stream stream3 = Stream.concat(stream1, stream2) ;
int max3 = stream3.max() ; // stream3 is NOT empty!
                           // max3 is -1

assert max == max3 ; // nope, max is 0

Bad luck, the property “max of the max” does not work anymore, our method max() does not work. Stating that the max() method returns an int is in fact a bug in itself.

Why that? Because the default value that we have to choose for the max(), in other words, the max() of the empty set, has to be the identity element of the max operation. And the problem, precisely, is that the max operation has no identity element.

Optional to the rescue

The max of an empty set is undefined, and to choose a value will lead to inconsistencies if we are not careful. Finding a correct solution to this problem is important because the max is not the only operation that has no identity elemnt. The same goes for the min, and also for the average operation…

The choice that was made is to state that the max() method returns an instance of Optional, a new concept introduced in Java 8. Incidentally, the min() method also returns an optional, as well as the average() method, for the same reasons.

The type Optional has been introduced to deal with the fact that a value may not exist, which is different from saying that it is null.

Optional: first patterns

There are two categories of patterns that have been implemented on the Optional class.

For the first category, an instance of Optional is the same kind of an instance of a wrapper class (Long, Double, etc…), in which you might have no value.

So we have two methods: isPresent() and get() to handle that.

Optional<Integer> opt = ... ;
if (opt.isPresent()) {
   int value = opt.get() ; // there is a value
} else {
   // decide what to do
}

We have then two variants of this pattern. The first one proposes a default value, which is valid in the context of our application, that we can write in two ways.

Optional<Person> opt = ... ;
// 1st way, decide of a default value
Person p1 = opt.orElse(Person.DEFAULT_PERSON) ;
// 2nd way, the same, lazily built
Person p2 =
   opt.orElseGet(() -> Person.DEFAULT_PERSON) ;

This first variant uses the orElseGet() method, that takes a Supplier as a parameter. This method acts as a lazy constructor, that will build the result object only if needed. Nice pattern, easy to write, thanks to the use of lambda expressions.

And the second variant, throws an exception, lazily constructed with the same pattern.

Optional<Person> opt = ... ;
// lazy construction of the exception
Person p1 = opt.orElseThrow(
   PersonNonExistentException::new) ;

Optional: second patterns

First version

An optional can also be seen from a different point of view, that will lead us to much more interesting patterns.

First of all, the Optional class has the same kind of methods we have on Stream: map(), filter(), and ifPresent().

Let us build an example on a NewMath class, that, instead of throwing exceptions or returning NaN when a result cannot be computed, will return Optional. This example is taken from this excellent book by Cay Horstman [1].

public class NewMath {

   public static Optional<Double> sqrt(Double d) {
      return d > 0 ?
         Optional.of(Math.sqrt(d)) ;
         Optional.empty() ;
   }

   public static Optional<Double> inv(Double d) {
      return d != 0 ?
         Optional.of(1/d) ;
         Optional.empty() ;
   }
}

This class is built on a simple idea. Instead of adding special cases (exceptions, special values), it always return optional, that will be empty if a returned value cannot be computed. The difference might look tiny, it is in fact fundametal.

Suppose we want to process a list of doubles with those methods.

double [] doubles = ... ; // an array of doubles
List<Double> result = new ArrayList<>() ;
doubles
   .stream()
   .forEach(
      d -> NewMath
             .sqrt(d)
             .ifPresent(
                result::add
             )
   ) ;

At the end of those operations, all the doubles that are valid have generated a value in the list result. The other values have been silently discarded from that result.

Using this new concept of optional allows to get rid of the if-then-else pattern, leading to more simple and cleaner code. But also faster.

The following question is: we have computed here the square root, can we compute the inverse of the square root? In other words, how can we chain operations?

A special method from Optional does exactly what we want: flatMap(). This method takes an optional as a parameter, and returns another optional. It has been added precisely to chain method calls. Our code is then the following.

double [] doubles = ... ; // an array of doubles
List<Double> result = new ArrayList<>() ;
doubles
   .stream()
   .forEach(
      d -> NewMath
             .inv(d)
             .flatMap(NewMath::sqrt)
             .ifPresent(
                result::add
             )
   ) ;

Second version

This code is interesting, it does what we need, but it still has a drawback. The call to ifPresent() takes a list that is built outside of our processing. If we want to go parallel with this processing (and that should be easy in Java 8), we will need a concurrent list, not an ArrayList. It would be better if the API could handle that for us.

In fact, what we need is a collector, and call a map() method instead of forEach().

For that, we are going to transform our optional in a stream. Our optional might be empty, we are going to change it into a stream, that will hold either nothing, either one value.

Let us take a closer look to the function we used in our previous code.

Function<Double, Optional<Double>> f =
   d -> NewMath.inv(d)
           .flatMap(NewMath::sqrt) ;

It is in fact possible to map this Optional into an Optional<Stream>. This optional will have the same semantic as the previous one: it can me empty.

Since it is an optional, one can call its orElse() method, and return an empty stream in case this optional is itself empty.

Function<Double, Stream<Double>> invSqrt =
   d -> NewMath.inv(d)
         .flatMap(NewMath::sqrt)
         .map(Stream::of)
         .orElseGet(Stream::empty) ;

This function works internaly with optionals, but hides them from the external code. It returns a stream, empty if the processed double is invalid (here null or negative), or holding the computed result.

We just have to use that function to write our processing using streams.

List<Double> doubles = Arrays.asList(-1d, 0d, 1d) ;

doubles.stream().parallel()
      .flatMap(invSqrt)
      .collect(Collectors.toList()) ;

The resut we get is the one we expect, the two invalid values have been removed from the result, in a silent way.

[1]

This code is pure stream code, so we can use a parallel stream without any problem, as well as all the other methods of the Stream interface.
tionnalités de l’API Stream.

Conclusion

This second way of using optionals is with no doubt the most interesting than the first one, that consist in checking if there is a value in it, and taking it out. Here we write things in a fluent way, and we process all the data without errors or exceptions. We just remove the the invalid data from the input stream, without the need for if-then-else. A new Java 8 pattern, leveraging optionals, stream, and the flat-map methods.

References

[1] Java SE 8 for the really impatient, Cay Horstman, Addison Wesley.