Lambda in the circle with code surrounding it

Functional Java – Optional do’s and don’ts


Here are some things you should know about using Optionals effectively. It is a useful tool that may or may not be appropriate for your use case. I hope this article will help you avoid making common mistakes with Optional.

Optional operations are not lazy

Optionals are not Streams. They are not lazy by nature. If you’re using Java 9 then you can use stream() method to transform eager Optional to a lazy Stream. In Java 8 this requires a custom utility method.

When you use Optional::orElse method you should remember that it does not behave the same way as if-else block. Instructions inside else branch of if-else block  run only if certain condition is not met (lazily). Any expression you put inside orElse() is always evaluated (eagerly). It can be as harmless as creating a String

String title =  optional.map(String::toUpperCase).orElse("NO TITLE");

but it can also be a hidden performance killer

private Resource resource;

public Optional<Resource> getResource() {

    return Optional.ofNullable(this.resource)
                    // if is present then apply reset function 
                   // on the value inside Optional
                   .map(Resource::reset)
                   // if not present then create new resource
                   // WRONG ! THIS ALWAYS EXECUTES
                   .orElse(createResource());
}

Code above works fine if a resource is absent. However if the resource is present then the expression inside orElse() is evaluated eagerly anyway. Hence each call to getResource() creates a new resource. This operation can be expensive for all we know.

Fix is very simple. Substitute orElse() with orElseGet(). This will delay the evaluation till it is required. orElseGet() takes Supplier as a parameter so you can pass lambdas to it.

    return Optional.ofNullable(this.resource)
                   .map(Resource::reset)
                   .orElseGet(() -> createResource());

As you can see orElse() is more suited for assigning literal values than flow control.

If instead of supplying a default value or running an action you would prefer to throw an exception when Optional is empty then you can use orElseThrow().

 return Optional.ofNullable(this.resource)
                .orElseThrow(RuntimeException::new);

Flow control

Java 8 does not give us a great way to control the flow when you are chaining Optionals. Not everything is intuitive and you have to know what you are doing. Most of the time if-else block or Stream is better and cleaner choice than Java 8 Optional.

Notice that map().orElseGet() solution is similar construct to if-else block. Code inside map() corresponds to if branch and code inside orElseGet() corresponds to else branch. Sometimes you have no need for the else branch. You can use ifPresent() in that case. ifPresent() takes Consumer as a parameter.

return Optional.ofNullable(this.resource)
               .ifPresent(a -> a.reset());

Java 9 brings some long awaited changes in Optional. My favorite by far is stream() method but there are also other goodies like or() and ifPresentOrElse().

// maps Optional to a Stream
Stream<T> stream​()

// if empty then get replacement Optional from Supplier
Optional<T> or​(Supplier<? extends Optional<? extends T>> supplier)

// do both branches in one method call
void ifPresentOrElse​(Consumer<? super T> action, Runnable emptyAction)

Optional is not an excuse for no input validation

Using Optionals does not excuse you from thinking about bad input. You should always validate your input. Return default value or fast-fail if you get bad data. If all you do all over your application is swallowing nulls then you will never get an Exception. Consequently you allow bad input to travel through your code and possibly produce some unexpected output.

Resource resource = null;
if(resource != null) {
  useResource(resource);
}
// possibly null travels through your code

Optional<Resource> optResource = Optional.empty();
if(optResource.isPresent()){
  useResource(optResource.get());
}
// possibly Optional.empty() travels through your code
optResource.map(a -> useResource(a));
// same here

If you are lucky nothing happens or you call a method using that travelling null reference and you get NullPointerException. Likewise, if you call get() on Optional.empty() you will get NoSuchElementException. Also your tests might detect the bug before code goes to production.

If you are unlucky, no exceptions will be thrown, your tests will not detect the bug and your clients will get bad output.

Forget about get()

Have a look at the two quotes below.

 I call it my billion-dollar mistake

– Tony Hoare, Inventor of null reference

 We should have never called it get(). We should have called it getOrThrowSomethingHorribleIfTheThingIsEmpty()

– Brian Goetz, Java Language Architect

if isPresent() then get() construct in reality is not very different from good old null checks. Not only exception is thrown if you call get() on an empty Optional but also forgetting to guard get() is just as easy as forgetting to guard null. There are alternative ways to use Optional that will produce better quality code.

Access object inside Optional

Using get() is not necessary in most cases. If all you want to do is retrieve a property value of an object that is boxed inside the Optional you can do the following

return optResource.map(Resource::getName).orElse("No name");

map() substitutes the value inside the Optional with the result of the function call. What you need to understand here is that orElse() method is the one that extracts the value from the Optional. In case there was no result default value is returned.

If you need the boxed object then you can use orElseGet() with Supplier . In the example below null object pattern can be used to take the burden of handling null away from the client.

// if empty then return resource that does nothing
return optResource.orElseGet(NullResource::new);

Use Optional as method return type

This is what primarily Optionals were created for. If you find yourself using Optional type for a field, parameter or in collection context then you should have a second look at your code. Optionals were introduced to represent no result in methods that are exposed to the outside world. Optionals can also be used whenever you need notion of result and a lack of it. Do not get carried away and put an Optional everywhere.

It is perfectly fine to use null for the fields you are using internally in the class. However you do not want to pass that null value to the client of your API. How is the client supposed to know what to do with it and what does null return value mean ? On the other hand if your client receives an Optional then there are only two possibilities. Result is present or result is empty.

Passing an Optional as an input parameter might be a sign that you are doing more than you should inside a function. Treat such code occurrence as a warning sign. Verify that you are not breaking single responsibility principle inside that code. Consider splitting the method or refactoring the code some other way.

Using an Optional in a collection or as a field for the objects that are inside that collection might not be a good idea. Optional consumes 4 x the memory and probably more CPU than a bare reference. If collection contains large number of elements then using Optionals instead of references can result in reduced performance.

No complex Optionals please

Immediately stop if you find yourself thinking about monster-like Optional with orElseGet() inside another orElseGet(). If you are using Java 9 then  or() method should be of use. In Java 8 you should probably be using a Stream or a bunch of some good ol’ if-else blocks with isPresent() checks.

// try to get resource from primary source
// if Optional.empty() then try secondary source
// if empty again then findFirst()
// returns Optional.empty()
Optional<Resource>  resource = Stream.of(
     this::primarySource, 
     this::secondarySource
    )
    .map(Supplier::get)
    .filter(Optional::isPresent)
    .map(Optional::get)
    .findFirst();

// or cleaner classic way
Optional<Resource> resource = this.primarySource();
if(!resource.isPresent()) {
    resource = this.secondarySource();
}

The more Suppliers you have the less readable if-else becomes. Stream code gives you more flexibility but it is not clear at first sight what it is doing.

Summary

  • Use Optional with caution. Do not forget about the eagerness of orElse().
  • Optionals are great as method return types and can potentially cause problems if used in fields, parameters and collections.
  • Optional::get should be avoided. This method is on a good path to deprecation.
  • If you’re using Java 9 you should definitely incorporate stream(), or() and ifPresentOrElse() methods to your project for cleaner code.
  • Keep your Optional simple. Do not force Optional to do something it was not designed for. Check if using short if, if-else blocks or Stream isn’t a better choice after all.
Thanks for reading ! Please
  •  
  •  
  • 1
  •  
  •  
  •  
  •  
  •  

Leave a comment

Your email address will not be published. Required fields are marked *