Java Basics: Optional

Recently, I noticed myself using Optional a lot more during my work and realized it would be a good topic for a Java Basics post. The Optional class was introduced in Java 8 and is primarily intended for use as return values from methods. Optional is basically a container for our return value that has an empty state for the null condition and also provides some additional functionality. The main idea is that the Optional class can help avoid a NullPointerException by forcing us to consider the case where there isn’t a return value.

Creating

It makes sense for us to start with learning how to create an Optional.

Let’s imagine that we’re creating a Calculator class and we want our divide method to absorb divide by zero issues.

public Optional<Long> divide(long dividend, long divisor) {
   if (divisor == 0) {
      return Optional.empty();
   }
       
   return Optional.of(dividend / divisor);
 }

If the divisor is zero, we return an empty Optional using the Optional.empty() method. When we receive proper input and can provide an answer we use Optional.of(). This is sufficient for our example because we’re using primitives, so there’s no chance we might pass a null value to the of method. Passing null to the of method will throw a NullPointerException.

If we have an object that might be null, we can use Optional.ofNullable().

Long possiblyNullAnswer = mysteryMethod();
Optional<Long> answer = Optional.ofNullable(possiblyNullAnswer);

In this awkwardly contrived example, we don’t know what we’re getting back from mysteryMethod, but we’re still safe. If possiblyNullAnswer is null, we’ll get an empty Optional instead of a NullPointerException.

Operations

Let’s take a look now at some of the basic operations we can perform once we have our Optional. After that, we’ll look at some of the cooler things we can do with it.

Let’s get a couple of Optional with which to work.

Calculator calc = new Calculator();
      
Optional<Long> emptyQuotient = calc.divide(15, 0);
Optional<Long> quotient = calc.divide(15, 3);

At our most basic, we can check to see if a value is present.

System.out.println("emptyQuotient.isPresent(): " 
   + emptyQuotient.isPresent());
System.out.println("quotient.isPresent(): " 
   + quotient.isPresent());

Starting in Java 11, we can also perform the check in the opposite way using isEmpty.

System.out.println("emptyQuotient.isEmpty(): " 
   + emptyQuotient.isEmpty());
System.out.println("quotient.isEmpty(): " 
   + quotient.isEmpty());

Both methods return a boolean. For isPresent, we’d get back false and true and for isEmpty true and false.

Accessing the Value

Many times, we need to work with the actual value encased in the Optional we receive. We have a lot of options for this.

If we’re certain we have a value or are comfortable with having a NoSuchElementException thrown if not, the most basic is the get method.

System.out.println("quotient.get() " + quotient.get());

This will gives us a Long with the number 5 in it. If we’d called this on our emptyQuotient, a NoSuchElementException would be thrown.

According to a note in the API documentation, however, the orElseThrow method (introduced in Java 10) is preferred over get. So let’s look at that next, followed by the related orElseThrow(Exception) method where we can provide an exception to throw.

try {
   System.out.println("emptyQuotient.orElseThrow() " 
      + emptyQuotient.orElseThrow());
} catch(NoSuchElementException e) {
   System.out.println("emptyQuotient.orElseThrow() "
      + e.getMessage());
}

If we want to specify a different exception to throw (I could see this being useful if we had a custom exception we wanted to use), the original version of the orElseThrow method takes an exception to throw.

try {
   System.out.println("quotient.orElseThrow() " 
      + emptyQuotient.orElseThrow(IllegalArgumentException::new));
} catch(IllegalArgumentException e) {
   System.out.println("quotient.orElseThrow() " 
      + e.getMessage());
}

If we have some default value we want to use if there’s no value present, we have a few options we can explore.

Let’s start with orElse, which will return the value or a default value you pass to it.

System.out.println("emptyQuotient.orElse() " 
   + emptyQuotient.orElse(0L));
System.out.println("quotient.orElse() " 
   + quotient.orElse(0L));

For emptyQuotient, we’ll get a zero back and we’ll get five (the result of 15/3) for quotient.

We can achieve the same result using orElseGet which is similar to orElse, but it takes a Supplier instead of a value. It will return the value or the result of the provided Supplier if empty.

System.out.println("emptyQuotient.orElseGet() " 
   + emptyQuotient.orElseGet(() -> 0L));
System.out.println("quotient.orElseGet() " 
   + quotient.orElseGet(() -> 0L));

There’s an important distinction between orElse and orElseGet that can effect performance in the case where the code executed for an empty Optional is more expensive. I could imagine a scenario where if the value wasn’t found we wrote a record to the database and returned that, for example.

Let’s spruce up our example by calling a method to get a random Long if the Optional is empty.

public Long random() {
   System.out.println("Generating a random long...");
   return new Random().nextLong();
}

Now we’ll call this method from both orElse and orElseGet and we’ll see the difference.

System.out.println("emptyQuotient.orElse() " 
   + emptyQuotient.orElse(calc.random()));
System.out.println("quotient.orElse() " 
   + quotient.orElse(calc.random()));
      
System.out.println("emptyQuotient.orElseGet() " 
   + emptyQuotient.orElseGet(() -> calc.random()));
System.out.println("quotient.orElseGet() " 
   + quotient.orElseGet(() -> calc.random()));

If we look closely at our output, we’ll see that when using orElse, calc.random is called even when the value is present.

Generating a random long...
emptyQuotient.orElse() -1894955205572599775
Generating a random long...
quotient.orElse() 5
Generating a random long...
emptyQuotient.orElseGet() 5838991833505410146
quotient.orElseGet() 5

There’s one last method for accessing our Optional that I’ve saved for last because, to be honest, I couldn’t think of a reason I’d want to do this. This is the of method. The of method either returns the Optional if a value is present or returns an Optional provided by the Supplier passed to it.

System.out.println("emptyQuotient.or() " 
   + emptyQuotient.or(() -> Optional.of(0L)));
System.out.println("quotient.or() " 
   + quotient.or(() -> Optional.of(0L)));

So our first call on emptyQuotient gives us Optional[0] and our second call basically gives us quotient back as Optional[5]. I’m sure someday I’ll run across some situation and think “oh! that’s what this is for!” but in the meantime, if any of you are using this in real life, I’d love to hear about it in the comments.

The Fun Stuff

We’ve covered the basics of testing our Optional and accessing the values, so let’s look at some of the more exciting operations that we can perform on an Optional.

If we want to just operate on our value if present, we can use ifPresent.

quotient.ifPresent((value) -> System.out.println("value * 6 = " + value * 6));

To perform one operation on the value if present, but run some other code if empty, let’s use ifPresentOrElse.

emptyQuotient.ifPresentOrElse((value) -> System.out.println("Found: " + value),
   () -> System.out.println("No value returned"));

We can also use some methods that we might associate with the Java Stream API on our Optional.

We could use the filter method to only operate on our value if it matches our filter specifications.

System.out.println("quotient.filter() on 5 " 
   + quotient.filter(value -> value == 5));
System.out.println("quotient.filter() on 3 " 
   + quotient.filter(value -> value == 3));

Likewise, we can use map and flatMap.

System.out.println(Optional.of(calc).map(c -> c.divide(15, 3)));

The results of the call above give use an Optional wrapped in an Optional.

Optional[Optional[5]]

This is where flatMap comes in by flattening the results, so that we don’t have a nested Optionals.

System.out.println(Optional.of(calc).flatMap(c -> c.divide(15, 3)));

The flatMap function doesn’t wrap the result in an additional optional the way map does, so if we check our output the result is “flatter.”

Optional[5]

We can also get a stream of our Optional which let’s us operate on it as though it’s a stream of one.

Let’s just quickly illustrate that by testing to see if our quotient contains 5 (our expected result of dividing 15 by 3).

System.out.println(quotient.stream().anyMatch((q) -> q == 5));

Our anyMatch call should give us back true.

Conclusion

In this post, we’ve looked at the Optional class and how to use it. For a more in-depth look at what the purpose of it is and how it came about, there’s a really excellent article about it put out by Oracle.

The example code is available on BitBucket.

Advertisement

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s