Using Optional as a better alternative to null - Effective Java 8 programming - Java 8 in Action: Lambdas, streams, and functional-style programming (2015)

Java 8 in Action: Lambdas, streams, and functional-style programming (2015)

Part 3. Effective Java 8 programming

Chapter 10. Using Optional as a better alternative to null

This chapter covers

· What’s wrong with null references and why you should avoid them

· From null to Optional: rewriting your domain model in a null-safe way

· Putting optionals to work: removing null checks from your code

· Different ways to read the value possibly contained in an optional

· Rethinking programming given potentially missing values

Raise your hand if you ever got a NullPointerException during your life as a Java developer. Keep it up if this is the Exception you encounter most frequently. Unfortunately, we can’t see you at this moment, but we believe there’s a very high probability that your hand is raised now. We also guess you may possibly be thinking something like “Yes, I agree, NullPointerExceptions are a pain for any Java developer, novice, or expert, but there’s not much we can do about them, because this is the price we pay to use such a convenient, and maybe unavoidable, construct as null references.” This is a common feeling in the (imperative) programming world; nevertheless, it may not be the whole truth but more likely a bias with solid historical roots.

A British computer scientist named Tony Hoare introduced null references back in 1965 while designing ALGOL W, one of the first typed programming languages with heap-allocated records, “simply because it was so easy to implement.” Despite his goal “to ensure that all use of references could be absolutely safe, with checking performed automatically by the compiler,” he decided to make an exception for null references, because he thought this was the most convenient way to model the absence of a value. After many years he regretted this decision, calling it “my billion-dollar mistake.” We’ve all seen the effect—we examine a field of an object, perhaps to determine whether its value is one of two expected forms, only to instead find we’re examining not an object but a null pointer that promptly raises that annoying NullPointerException.

In reality, Hoare’s statement could underestimate the costs incurred by millions of developers fixing bugs caused by null references in the last 50 years. Indeed, the vast majority of the languages[1] created in recent decades, including Java, have been built with the same design decision, maybe for reasons of compatibility with older languages, or more probably, as Hoare states, “simply because it was so easy to implement.” Let’s start by looking at a simple example to understand the problems with null.

1 Notable exceptions include most typed functional languages, such as Haskell and ML; these include algebraic data types that allow data types to be succinctly expressed, including explicit specification of whether special values such as null are to be included on a type-by-type basis.

10.1. How do you model the absence of a value?

Imagine you have the following nested object structure for a person owning a car and having car insurance.

Listing 10.1. The Person/Car/Insurance data model

public class Person {

private Car car;

public Car getCar() { return car; }

}

public class Car {

private Insurance insurance;

public Insurance getInsurance() { return insurance; }

}

public class Insurance {

private String name;

public String getName() { return name; }

}

Then, what’s possibly problematic with the following code?

public String getCarInsuranceName(Person person) {

return person.getCar().getInsurance().getName();

}

This code looks pretty reasonable, but many people don’t own a car. So what’s the result of calling the method getCar? A common unfortunate practice is to return the null reference to indicate the absence of a value, here to indicate the absence of a car. As a consequence, the call togetInsurance will return the insurance of a null reference, which will result in a NullPointerException at run-time and stop your program from running further. But that’s not all. What if person was null? What if the method getInsurance returned null too?

10.1.1. Reducing NullPointerExceptions with defensive checking

What can you do to avoid running into an unexpected NullPointerException? Typically, you can add null checks where necessary (and sometimes, in an excess of defensive programming, even where not necessary) and often with different styles. A first attempt to write a method preventing a NullPointerException is shown in the following listing.

Listing 10.2. Null-safe attempt 1: deep doubts

This method performs a null check every time it dereferences a variable, returning the string “Unknown” if any of the variables traversed in this dereferencing chain is a null value. The only exception to this is that you’re not checking to see if the name of the insurance company is nullbecause, like any other company, you know it must have a name. Note that you can avoid this last check only because of your knowledge of the business domain, but that isn’t reflected in the Java classes modeling your data.

We labeled the method in listing 10.2 “deep doubts” because it shows a recurring pattern: every time you have a doubt that a variable could be null, you’re obliged to add a further nested if block, increasing the indentation level of the code. This clearly scales poorly and compromises the readability, so maybe you’d like to attempt another solution. Let’s try to avoid this problem by doing something different in the next listing.

Listing 10.3. Null-safe attempt 2: too many exits

In this second attempt, you try to avoid the deeply nested if blocks, adopting a different strategy: every time you meet a null variable, you return the string “Unknown.” Nevertheless, this solution is also far from ideal; now the method has four distinct exit points, making it hardly maintainable. Even worse, the default value to be returned in case of a null, the string “Unknown,” is repeated in three places—and hopefully not misspelled! Of course, you may wish to extract it into a constant to avoid this problem.

Furthermore, it’s an error-prone process; what if you forget to check that one property could be null? We argue in this chapter that using null to represent the absence of a value is the wrong approach. What you need is a better way to model the absence and presence of a value.

10.1.2. Problems with null

To recap our discussion so far, the use of null references in Java causes both theoretical and practical problems:

· It’s a source of error. NullPointerException is by far the most common exception in Java.

· It bloats your code. It worsens readability by making it necessary to fill your code with often deeply nested null checks.

· It’s meaningless. It doesn’t have any semantic meaning, and in particular it represents the wrong way to model the absence of a value in a statically typed language.

· It breaks Java philosophy. Java always hides pointers from developers except in one case: the null pointer.

· It creates a hole in the type system. null carries no type or other information, meaning it can be assigned to any reference type. This is a problem because, when it’s propagated to another part of the system, you have no idea what that null was initially supposed to be.

To provide some context for what other solutions are out there for this problem, let’s briefly look at what other programming languages have to offer.

10.1.3. What are the alternatives to null in other languages?

In recent years other languages like Groovy worked around this problem by introducing a safe navigation operator, represented by ?., to safely navigate through potentially null values. To understand how this works, consider the following Groovy code to retrieve the name of the insurance company used by a given person to insure their car:

def carInsuranceName = person?.car?.insurance?.name

What this statement does should be pretty clear. A person might not have a car and you tend to model this possibility by assigning a null to the car reference of the Person object. Similarly, a car might not have insurance. The Groovy safe navigation operator allows you to safely navigate through these potentially null references without throwing a NullPointerException, by just propagating the null reference through the invocations chain, returning a null in the event that any value in the chain is a null.

A similar feature was proposed and then discarded for Java 7. Somehow, though, we don’t seem to miss a safe navigation operator in Java; the first temptation of all Java developers when confronted with a NullPointerException is to quickly fix it by adding an if statement, checking that a value is not null before invoking a method on it. If you solve this problem in this way, without wondering if it’s correct that your algorithm or your data model could present a null value in that specific situation, you’re not fixing a bug but hiding it, making its discovery and fix far more difficult for whoever will be called to work on it next time; it very likely will be you in the next week or month. You’re just sweeping the dirt under the carpet. Groovy’s null-safe dereferencing operator is only a bigger and more powerful broom for making this mistake, without worrying too much about its consequences.

Other functional languages, such as Haskell and Scala, take a different view. Haskell includes a Maybe type, which essentially encapsulates an optional value. A value of type Maybe can contain either a value of a given type or nothing. There’s no concept of a null reference. Scala has a similar construct called Option[T] to encapsulate the presence or absence of a value of type T, which we discuss in chapter 15. You then have to explicitly check whether a value is present or not using operations available on the Option type, which enforces the idea of “null checking.” You can no longer forget to do it because it’s enforced by the type system.

Okay, we diverged a bit, and all this sounds fairly abstract. You might now wonder “So, what about Java 8?” Well actually, Java 8 takes inspiration from this idea of an “optional value” by introducing a new class called java.util.Optional<T>! In this chapter, we show the advantages of using it to model potentially absent values instead of assigning a null reference to them. We also clarify how this migration from nulls to Optionals requires you to rethink the way you deal with optional values in your domain model. Finally, we explore the features of this newOptional class and provide a few practical examples showing how to use it effectively. Ultimately, you’ll learn how to design better APIs in which—just by reading the signature of a method—users can tell whether to expect an optional value.

10.2. Introducing the Optional class

Java 8 introduces a new class called java.util.Optional<T> that’s inspired by the ideas of Haskell and Scala. It’s a class that encapsulates an optional value. This means, for example, that if you know a person might or might not have a car, the car variable inside the Person class shouldn’t be declared type Car and assigned to a null reference when the person doesn’t own a car, but instead should be type Optional<Car>, as illustrated in figure 10.1.

Figure 10.1. An optional Car

When a value is present, the Optional class just wraps it. Conversely, the absence of a value is modeled with an “empty” optional returned by the method Optional.empty. It’s a static factory method that returns a special singleton instance of the Optional class. You might wonder what the difference is between a null reference and Optional .empty(). Semantically, they could be seen as the same thing, but in practice the difference is huge: trying to dereference a null will invariably cause a NullPointer-Exception, whereas Optional.empty() is a valid, workable object of type Optional that can be invoked in useful ways. You’ll soon see how.

An important, practical semantic difference in using optionals instead of nulls is that in the first case, declaring a variable of type Optional<Car> instead of Car clearly signals that a missing value is permitted there. Conversely, always using the type Car and possibly assigning a nullreference to a variable of that type implies you don’t have any help, other than your knowledge of the business model, to understand whether the null belongs to the valid domain of that given variable or not.

With this in mind, you can rework the original model from listing 10.1, using the Optional class as follows.

Listing 10.4. Redefining the Person/Car/Insurance data model using Optional

Note how the use of the Optional class enriches the semantics of your model. The fact that a person references an Optional<Car>, and a car an Optional<Insurance>, makes it explicit in the domain that a person might or might not own a car, and that car might or might not be insured.

At the same time, the fact that the name of the insurance company is declared of type String instead of Optional<String> makes it evident that it’s mandatory for an insurance company to have a name. This way you know for certain whether you’ll get a NullPointerException when dereferencing the name of an insurance company; you don’t have to add a null check because doing so will just hide the problem instead of fixing it. An insurance company must have a name, so if you find one without, you’ll have to work out what’s wrong in your data instead of adding a piece of code covering up this circumstance. Using optionals consistently disambiguates beyond any doubt the case of a value that can be structurally missing from the case of a value that’s absent only because of a bug in your algorithm or a problem in your data. It’s important to note that the intention of the Optional class is not to replace every single null reference. Instead, its purpose is to help you design more-comprehensible APIs so that by just reading the signature of a method, you can tell whether to expect an optional value. This forces you to actively unwrap an optional to deal with the absence of a value.

10.3. Patterns for adopting Optional

So far, so good; you’ve learned how to employ optionals in types to clarify your domain model and the advantages this offers over representing missing values with null references. But how can you use them now? What can you do with them, or more specifically how can you actually use a value wrapped in an optional?

10.3.1. Creating Optional objects

The first step before working with Optional is to learn how to create optional objects! There are several ways.

Empty optional

As mentioned earlier, you can get hold of an empty optional object using the static factory method Optional.empty:

Optional<Car> optCar = Optional.empty();

Optional from a non-null value

You can also create an optional from a non-null value with the static factory method Optional.of:

Optional<Car> optCar = Optional.of(car);

If car were null, a NullPointerException would be immediately thrown (rather than getting a latent error once you try to access properties of the car).

Optional from null

Finally, by using the static factory method Optional.ofNullable, you can create an Optional object that may hold a null value:

Optional<Car> optCar = Optional.ofNullable(car);

If car were null, the resulting Optional object would be empty.

You might imagine we’ll continue by investigating “how to get a value out of an optional.” In particular, there’s a get method that does precisely this, and we’ll talk more about it later. But get raises an exception when the optional is empty, and so using it in an ill-disciplined manner effectively re-creates all the maintenance problems caused by using null. So instead we start by looking at ways of using optional values that avoid explicit tests; these are inspired by similar operations on streams.

10.3.2. Extracting and transforming values from optionals with map

A common pattern is to extract information from an object. For example, you may want to extract the name from an insurance company. You’d need to check whether insurance is null before extracting the name as follows:

String name = null;

if(insurance != null){

name = insurance.getName();

}

Optional supports a map method for this pattern. It works as follows (from here on we use the model presented in listing 10.4):

Optional<Insurance> optInsurance = Optional.ofNullable(insurance);

Optional<String> name = optInsurance.map(Insurance::getName);

It’s conceptually similar to the stream’s map method you saw in chapters 4 and 5. The map operation applies the provided function to each element of a stream. You could also think of an Optional object as a particular collection of data, containing at most a single element. If the Optionalcontains a value, then the function passed as argument to map transforms that value. If the Optional is empty, then nothing happens. Figure 10.2 illustrates this similarity, showing what happens when passing a function that transforms a square into a triangle to the map methods of both a stream of square and an optional of square.

Figure 10.2. Comparing the map methods of Streams and Optionals

This looks useful, but how could you use this to write the previous code, which was chaining several method calls in a safe way?

public String getCarInsuranceName(Person person) {

return person.getCar().getInsurance().getName();

}

We have to look at another method supported by Optional called flatMap!

10.3.3. Chaining Optional objects with flatMap

Because you’ve just learned how to use map, your first reaction may be that you can rewrite the previous code using map as follows:

Optional<Person> optPerson = Optional.of(person);

Optional<String> name =

optPerson.map(Person::getCar)

.map(Car::getInsurance)

.map(Insurance::getName);

Unfortunately, this code doesn’t compile. Why? The variable optPeople is of type Optional<People>, so it’s perfectly fine to call the map method. But getCar returns an object of type Optional<Car> (as presented in listing 10.4). This means the result of the map operation is an object of type Optional<Optional<Car>>. As a result, the call to getInsurance is invalid because the outermost optional contains as its value another optional, which of course doesn’t support the getInsurance method. Figure 10.3 illustrates the nested optional structure you’d get.

Figure 10.3. A two-level optional

So how can we solve this problem? Again, we can look at a pattern you’ve used previously with streams: the flatMap method. With streams, the flatMap method takes a function as an argument, which returns another stream. This function is applied to each element of a stream, which would result in a stream of streams. But flatMap has the effect of replacing each generated stream by the contents of that stream. In other words, all the separate streams that are generated by the function get amalgamated or flattened into a single stream. What you want here is something similar, but you want to flatten a two-level optional into one.

Like figure 10.2 for the map method, figure 10.4 illustrates the similarities between the flatMap methods of the Stream and Optional classes.

Figure 10.4. Comparing the flatMap methods of Stream and Optional

Here the function passed to the stream’s flatMap method transforms each square into another stream containing two triangles. The result of a simple map would then be a stream containing three other streams, each of them having two triangles, but the flatMap method flattens this two-level stream into a single stream containing six triangles in total. In the same way, the function passed to the optional’s flatMap method transforms the square contained in the original optional into an optional containing a triangle. If this function was passed to the map method, the result would be an optional containing another optional that, in turn, contains a triangle, but the flatMap method flattens this two-level optional into a single optional containing a triangle.

Finding a car’s insurance company name with optionals

Now that you know the theory of the map and flatMap methods of Optional, let’s put them into practice. The ugly attempts we made in listings 10.2 and 10.3 can be rewritten using the optional-based data model of listing 10.4 as follows.

Listing 10.5. Finding a car’s insurance company name with Optionals

Comparing listing 10.5 with the two former attempts shows the advantages of using optionals when dealing with potentially missing values. This time, you can obtain what you want with an easily comprehensible statement—instead of increasing the code complexity with conditional branches.

In implementation terms, first note that you modify the signature of the getCarInsuranceName method from listings 10.2 and 10.3, because we explicitly said there could also be a case where a nonexistent Person is passed to this method, such as when that Person is retrieved from a database using an identifier, and you want to model the possibility that no Person exists in your data for the given identifier. You model this additional requirement, changing the type of the method’s argument from Person to Optional<Person>.

Once again this approach allows you to make explicit through the type system something that otherwise would remain implicit in your knowledge of the domain model, namely, you should never forget that the first purpose of a language, even a programming language, is communication. Declaring a method to take an optional as an argument or to return an optional as a result documents to your colleagues—and all future users of your method—that it can take an empty value or that it might give an empty value as result.

Person/Car/Insurance dereferencing chain using optionals

Starting with this Optional<Person>, the Car from the Person, the Insurance from the Car, and the String containing the insurance company name from the Insurance are dereferenced with a combination of the map and flatMap methods introduced earlier. Figure 10.5 illustrates this pipeline of operations.

Figure 10.5. The Person/Car/Insurance dereferencing chain using optionals

Here you begin with the optional wrapping the Person and invoking flatMap(Person::getCar)on it. As we said, you can logically think of this invocation as something that happens in two steps. In step 1, a Function is applied to the Person inside the optional to transform it. In this case, the Function is expressed with a method reference invoking the method getCar on that Person. Because that method returns an Optional<Car>, the Person inside the optional is transformed into an instance of that type, resulting in a two-level optional that’s flattened as part of the flatMap operation. From a theoretical point of view, you can think of this flattening operation as the operation that combines two optionals, resulting in an empty optional, if at least one of them is empty. What happens in reality is that if you invoke flatMap on an empty optional, nothing is changed, and it’s returned as is. Conversely, if the optional wraps a Person, the Function passed to the flatMap method is applied to that Person. Because the value produced by that Function application is already an optional, the flatMap method can return it as is.

The second step is similar to the first one, transforming the Optional<Car> into an Optional<Insurance>. Step 3 turns the Optional<Insurance> into an Optional<String>: because the Insurance.getName() method returns a String, in this case a flatMap isn’t necessary.

At this point the resulting optional will be empty if any of the methods in this invocation chain returns an empty optional or will contain the desired insurance company name otherwise. So how do you read that value? After all, you’ll end up getting an Optional<String> that may or may not contain the name of the insurance company. In listing 10.5, we used another method called orElse, which provides a default value in case the optional is empty. There are many methods to provide default actions or unwrap an optional. Let’s look at them in more detail.

Using optionals in a domain model and why they’re not Serializable

In listing 10.4, we showed how to use Optionals in your domain model in order to mark with a specific type the values that are allowed to be missing or remain undefined. However, the designers of the Optional class developed it from different assumptions and with a different use case in mind. In particular, Java Language Architect Brian Goetz clearly stated the purpose of Optional is to support the optional-return idiom only.

Because the Optional class wasn’t intended for use as a field type, it also doesn’t implement the Serializable interface. For this reason, using Optionals in your domain model could break applications using tools or frameworks that require a serializable model to work. Nevertheless, we believe that we showed why using Optionals as a proper type in your domain is a good idea, especially when you have to traverse a graph of objects that could be, all or in part, potentially not present. Alternatively, if you need to have a serializable domain model, we suggest you at least provide a method allowing access also to any possibly missing value as an optional, as in the following example:

public class Person {

private Car car;

public Optional<Car> getCarAsOptional() {

return Optional.ofNullable(car);

}

}

10.3.4. Default actions and unwrapping an optional

We decided to read this value using the orElse method that allows you to also provide a default value that will be returned in the case of an empty optional. The Optional class provides several instance methods to read the value contained by an Optional instance.

· get() is the simplest but also the least safe of these methods. It returns the wrapped value if present but throws a NoSuchElementException otherwise. For this reason, using this method is almost always a bad idea unless you’re really sure the optional contains a value. In addition, it’s not much of an improvement over nested null checks.

· orElse(T other) is the method used in listing 10.5, and as we noted there, it allows you to provide a default value for when the optional doesn’t contain a value.

· orElseGet(Supplier<? extends T> other) is the lazy counterpart of the orElse method, because the supplier is invoked only if the optional contains no value. You should use this method either when the default value is time-consuming to create (to gain a little efficiency) or you want to be sure this is done only if the optional is empty (in which case it’s strictly necessary).

· orElseThrow(Supplier<? extends X> exceptionSupplier) is similar to the get method in that it throws an exception when the optional is empty, but in this case it allows you to choose the type of exception that you want to throw.

· ifPresent(Consumer<? super T> consumer) lets you execute the action given as argument if a value is present; otherwise no action is taken.

The analogies between the Optional class and the Stream interface aren’t limited to the map and flatMap methods. There’s a third method, filter, that behaves in a similar fashion, and we explore it in section 10.3.6.

10.3.5. Combining two optionals

Let’s now suppose that you have a method that given a Person and a Car queries some external services and implements some quite complex business logic to find the insurance company offering the cheapest policy for that combination:

public Insurance findCheapestInsurance(Person person, Car car) {

// queries services provided by the different insurance companies

// compare all those data

return cheapestCompany;

}

Let’s also suppose that you want to develop a null-safe version of this method taking two optionals as arguments and then returning an Optional<Insurance> that will be empty if at least one of the values passed in to it is also empty. The Optional class also provides an isPresentmethod returning true if the optional contains a value, so your first attempt could be to implement this method as follows:

public Optional<Insurance> nullSafeFindCheapestInsurance(

Optional<Person> person, Optional<Car> car) {

if (person.isPresent() && car.isPresent()) {

return Optional.of(findCheapestInsurance(person.get(), car.get()));

} else {

return Optional.empty();

}

}

This method has the advantage of making clear in its signature that both the person and the car values passed to it could be missing and that for this reason it couldn’t return any value. Unfortunately, its implementation resembles too closely the null checks that you’d write if the method took as arguments a Person and a Car and both those arguments could be potentially null. Is there a better and more idiomatic way to implement this method using the features of the Optional class? Take a few minutes to go through Quiz 10.1 and try to find an elegant solution.

Quiz 10.1: Combining two optionals without unwrapping them

Using a combination of the map and flatMap methods you learned in this section, rewrite the implementation of the former nullSafeFindCheapestInsurance() method in a single statement.

Answer:

You can implement that method in a single statement and without using any conditional constructs like the ternary operator as follows:

public Optional<Insurance> nullSafeFindCheapestInsurance(

Optional<Person> person, Optional<Car> car) {

return person.flatMap(p -> car.map(c -> findCheapestInsurance(p, c)));

}

Here you invoke a flatMap on the first optional, so if this is empty, the lambda expression passed to it won’t be executed at all and this invocation will just return an empty optional. Conversely, if the person is present, it uses it as the input of a Function returning anOptional<Insurance> as required by the flatMap method. The body of this function invokes a map on the second optional, so if it doesn’t contain any car, the Function will return an empty optional and so will the whole nullSafeFindCheapestInsurance method. Finally, if both the person and the car are present, the lambda expression passed as argument to the map method can safely invoke the original findCheapestInsurance method with them.

The analogies between the Optional class and the Stream interface aren’t limited to the map and flatMap methods. There’s a third method, filter, that behaves in a similar fashion on both classes, and we explore it next.

10.3.6. Rejecting certain values with filter

Often you need to call a method on an object and check some property. For example, you might need to check whether the insurance’s name is equal to “Cambridge-Insurance.” To do this in a safe way, you first need to check whether the reference pointing to an Insurance object is nulland then call the getName method, as follows:

Insurance insurance = ...;

if(insurance != null && "CambridgeInsurance".equals(insurance.getName())){

System.out.println("ok");

}

This pattern can be rewritten using the filter method on an Optional object, as follows:

Optional<Insurance> optInsurance = ...;

optInsurance.filter(insurance ->

"CambridgeInsurance".equals(insurance.getName()))

.ifPresent(x -> System.out.println("ok"));

The filter method takes a predicate as an argument. If a value is present in the Optional object and it matches the predicate, the filter method returns that value; otherwise, it returns an empty Optional object. If you remember that you can think of an optional as a stream containing at most a single element, the behavior of this method should be pretty clear. If the optional is already empty, it doesn’t have any effect; otherwise, it applies the predicate to the value contained in the optional. If this application returns true, the optional returns unchanged; otherwise, the value is filtered away, leaving the optional empty. You can test your understanding of how the filter method works by working through Quiz 10.2.

Quiz 10.2: Filtering an optional

Supposing the Person class of our Person/Car/Insurance model also has a method getAge to access the age of the person, modify the getCarInsuranceName method in listing 10.5 using the following signature

public String getCarInsuranceName(Optional<Person> person, int minAge)

so that the insurance company name is returned only if the person has an age greater than or equal to the minAge argument.

Answer:

You can filter from the Optional the person it eventually contains if the age of the person is greater than the minAge argument by encoding this condition in a predicate and passing this predicate to the filter method as follows:

public String getCarInsuranceName(Optional<Person> person, int minAge) {

return person.filter(p -> p.getAge() >= minAge)

.flatMap(Person::getCar)

.flatMap(Car::getInsurance)

.map(Insurance::getName)

.orElse("Unknown");

}

In the next section, we investigate the remaining features of the Optional class and give more practical examples showing various techniques you could use to reimplement the code you write to manage missing values.

Table 10.1 summarizes the methods of the Optional class.

Table 10.1. The methods of the Optional class

Method

Description

empty

Returns an empty Optional instance

filter

If the value is present and matches the given predicate, returns this Optional; otherwise returns the empty one

flatMap

If a value is present, returns the Optional resulting from the application of the provided mapping function to it; otherwise returns the empty Optional

get

Returns the value wrapped by this Optional if present; otherwise throws a NoSuchElementException

ifPresent

If a value is present, invokes the specified consumer with the value; otherwise does nothing

isPresent

Returns true if there is a value present; otherwise false

map

If a value is present, applies the provided mapping function to it

of

Returns an Optional wrapping the given value or throws a NullPointerException if this value is null

ofNullable

Returns an Optional wrapping the given value or the empty Optional if this value is null

orElse

Returns the value if present or the given default value otherwise

orElseGet

Returns the value if present or the one provided by the given Supplier otherwise

orElseThrow

Returns the value if present or throws the exception created by the given Supplier otherwise

10.4. Practical examples of using Optional

As you’ve learned, effective use of the new Optional class implies a complete rethink of how you deal with potentially missing values. This rethink involves not only the code you write but, possibly even more important, how you interact with native Java APIs.

Indeed, we believe that many of those APIs would have been written differently if the Optional class had been available at the time they were developed. For backward-compatibility reasons, old Java APIs can’t be changed to make proper use of optionals, but all is not lost. You can fix, or at least work around, this issue by adding into your code small utility methods that allow you to benefit from the power of optionals. You’ll see how to do this with a couple of practical examples.

10.4.1. Wrapping a potentially null value in an optional

An existing Java API almost always returns a null to signal the absence of the required value or that the computation to obtain it failed for some reason. For instance, the get method of a Map returns null as its value if it contains no mapping for the requested key. But for the reasons we listed earlier, in most cases like this, you’d prefer that these methods could return an optional. You can’t modify the signature of these methods, but you can easily wrap the value they return with an optional. Continuing with the Map example, and supposing you have a Map<String,Object>, then accessing the value indexed by key with

Object value = map.get("key");

will return null if there’s no value in the map associated with the String “key.” You can improve this by wrapping in an optional the value returned by the map. You can do this in two ways: either with an ugly if-then-else adding to code complexity or by using the methodOptional.ofNullable that we discussed earlier:

Optional<Object> value = Optional.ofNullable(map.get("key"));

You can use this method every time you want to safely transform a value that could be potentially null into an optional.

10.4.2. Exceptions vs. Optional

Throwing an exception is another common alternative in the Java API to returning a null when, for any reason, a value can’t be provided. A typical example of this is the conversion of String into an int, provided by the Integer.parseInt(String) static method. In this case, if theString doesn’t contain a parseable integer, this method throws a NumberFormatException. The net effect is once again that the code signals an invalid argument in the case of a String not representing an integer, the only difference being that this time you have to check it with atry/catch block instead of using an if condition controlling whether a value is not null.

You could also model the invalid value caused by nonconvertible Strings with an empty optional, so you’d prefer that parseInt could return an optional. You can’t change the original Java method, but nothing prevents you from implementing a tiny utility method, wrapping it, and returning an optional as desired, as shown in this next listing.

Listing 10.6. Converting a String into an Integer returning an optional

Our suggestion is to collect several methods similar to this in a utility class; let’s call it OptionalUtility. In this way, from now on you’ll always be allowed to convert a String into an Optional<Integer>, using this OptionalUtility.stringToInt method. You can forget that you encapsulated the ugly try/catch logic in it.

Primitive optionals and why you shouldn’t use them

Note that, like streams, optionals also have primitive counterparts—OptionalInt, OptionalLong, and OptionalDouble—so the method in listing 10.6 could have returned an OptionalInt instead of Optional<Integer>. In chapter 5, we encouraged the use of primitive streams, especially when they could contain a huge number of elements, for performance reasons, but because an Optional can have at most a single value, that justification doesn’t apply here.

We discourage using primitive optionals because they lack the map, flatMap, and filter methods, which, as you saw in section 10.2, are the most useful methods of the Optional class. Moreover, as happens for streams, an optional can’t be composed with its primitive counterpart, so, for example, if the method of listing 10.6 returned OptionalInt, you couldn’t pass it as a method reference to the flatMap method of another optional.

10.4.3. Putting it all together

To demonstrate how the methods of the Optional class presented so far can be used together in a more compelling use case, suppose you have some Properties that are passed as configuration arguments to your program. For the purpose of this example and to test the code you’ll develop, create some sample Properties as follows:

Properties props = new Properties();

props.setProperty("a", "5");

props.setProperty("b", "true");

props.setProperty("c", "-3");

Now let’s also suppose your program needs to read a value from these Properties that it will interpret as a duration in seconds. Because a duration has to be a positive number, you’ll want a method with the following signature

public int readDuration(Properties props, String name)

that, when the value of a given property is a String representing a positive integer, returns that integer, but returns zero in all other cases. To clarify this requirement you can formalize it with a few JUnit assertions:

assertEquals(5, readDuration(param, "a"));

assertEquals(0, readDuration(param, "b"));

assertEquals(0, readDuration(param, "c"));

assertEquals(0, readDuration(param, "d"));

These assertions reflect the original requirement: the readDuration method returns 5 for the property "a" because the value of this property is a String that’s convertible in a positive number; it returns 0 for "b" because it isn’t a number, returns 0 for "c" because it’s a number but it’s negative, and returns 0 for "d" because a property with that name doesn’t exist. Let’s try to implement the method satisfying this requirement in imperative style, as shown in the following listing.

Listing 10.7. Reading duration from a property imperatively

As you might expect, the resulting implementation is quite convoluted and not very readable, presenting multiple nested conditions coded both as if statements and as a try/catch block. Take a few minutes to figure out in Quiz 10.3 how you can achieve the same result using what you’ve learned in this chapter.

Quiz 10.3: Reading duration from a property using an optional

Using the features of the Optional class and the utility method of listing 10.6, try to reimplement the imperative method of listing 10.7 with a single fluent statement.

Answer:

Because the value returned by the Properties.getProperty(String) method is a null when the required property doesn’t exist, it’s convenient to turn this value into an optional with the ofNullable factory method. You can then convert the Optional<String> into anOptional<Integer>, passing to its flatMap method a reference to the OptionalUtility.stringToInt method developed in listing 10.6. Finally, you can easily filter away the negative number. In this way, if any of these operations will return an empty optional, the method will return the 0 that’s passed as the default value to the orElse method; otherwise, it will return the positive integer contained in the optional. This is then simply implemented as follows:

public int readDuration(Properties props, String name) {

return Optional.ofNullable(props.getProperty(name))

.flatMap(OptionalUtility::stringToInt)

.filter(i -> i > 0)

.orElse(0);

}

Note the common style in using optionals and streams; both are reminiscent of a database query where several operations are chained together.

10.5. Summary

In this chapter, you’ve learned the following:

· null references have been historically introduced in programming languages to generally signal the absence of a value.

· Java 8 introduces the class java.util.Optional<T> to model the presence or absence of a value.

· You can create Optional objects with the static factory methods Optional.empty, Optional.of, and Optional.ofNullable.

· The Optional class supports many methods such as map, flatMap, and filter, which are conceptually similar to the methods of a stream.

· Using Optional forces you to actively unwrap an optional to deal with the absence of a value; as a result, you protect your code against unintended null pointer exceptions.

· Using Optional can help you design better APIs in which, just by reading the signature of a method, users can tell whether to expect an optional value.