The Optional
class was introduced with Java 8, and even though for many developers it’s just a way to handle differently the null
check for variables/instances its real purpose is to introduce functional style coding in Java. The idea behind this article is to provide some best practices using the Optional giving a functional code style and simplifying our code.
Creating an Optional
We have 2 methods to create Optional instances:
Optional.of(value)
Optional.ofNullable(value)
Optional<String> myString1 = Optional.of("Hello world!"); //populated optional Optional<String> myString2 = Optional.of(null); //Throws an NullPointer Exception Optional<String> myString3 = Optional.ofNullable("Hello world!"); //populated optional Optional<String> myString4 = Optional.ofNullable(null); //empty optional
Let’s use Optional.ofNullable
when possible then we will handle correctly any null
value. As we can see in the above code, using Optional.of(null)
will throw a NullPointerException
, therefore is better only to use it when you are 100% the value will not be null
.
isPresent() and isEmpty()
We can use the instance method of an Optional isPresent and isEmpty to know whether the instance has a value or not. That’s exactly the code that we want to avoid!
Optional<String> myOptional = doSomething(); String myValue; if (myOptional.isPresent()) { myValue = myOptional.get(); } else { myValue = null; }
There is no bigger difference between using the old null
(var != null)
check to the isPresent
or isEmpty
method. We are not taking advantage of the benefits of the Optional, and in fact, our code looks more verbose. If we really want to take advantage of its benefits, let’s switch the mindset and let’s have closer functional programming thinking.
Optional and functional style code
We will see how most of our code using Optional
can be enhanced using methods like map
, filter
, orElse
, orElseThrow
, ifPresentOrElse
, reducing the use of the isEmpty
and isPresent
methods at the minimum, at the same time, doing your code more readable.
For example, instead of using the if(optional.isPresent)
statement, we can replace it with the ifPresentOrElse
or the only the ifPresent
, to execute some logic.
String myValue = null; Optional<String> myOptional = doSomething(); myOptional.ifPresent(foo -> { doMoreThings(foo);/** more code here **/});
Car myCar; Optional<Car> myOptionalCar = doSomethingCar(); myOptional.ifPresentOrElse( foo -> { /** more code here **/ }, () -> { /** more code here **/ } );
Some times, we don’t have to execute some logic, but maybe to get the value of the Optional and store it in a variable. For those use cases, we can use the orElse
method
Optional<Foo> possibleFoo = doSomething(); Foo foo = possibleFoo.orElse(new Foo());
or doing it lazily with the orElseGet
(…) method.
Optional<Foo> possibleFoo = doSomething(); Foo foo = possibleFoo.orElseGet(() -> {/**Some logic that returns a Foo**/});
And what about throwing an Exception when the Optional is empty, for example, finding a record in the database. For that purpose, we have the orElseThrow(()-> {...})
method.
Optional<User> maybeUser = findUserById(userId); User user = maybeUser.orElseThrow(() -> new NotFoundException("User not found")); //If user is present we continue here.
Some other important methods from the Optional
class that we can use map
and filter
. We can use the map to transform it into another object, one advantage is that if the Optional is empty then it map logic will not be called.
Optional<User> maybeUser = findUserById(userId); String userName = maybeUser.map(u -> user.getName()+ " " + user.getLastname()) .orElseThrow(() -> new NotFoundException("User not found"));
We use the filter
method to reduce the result if the Optional
is not empty. If the filter criteria is false
then it will return an empty Optional
.
Optional<User> maybeUser = findUserById(userId); User adultUser = maybeUser.filter(u -> u.getAge() > 18 ) .orElseThrow(() -> new NotFoundException("User is not an adult"));
And finally, we can notice how we can use more than one of the Optional
methods in the same line, for example: map -> filter -> map-> getOrElse. So our code will require less code lines.
When and when not to use Optional
As we have seen Optional
is excellent to be used as a return type (this is its purpose), but what avoid fields
or method parameters
. I recommend following these best practices.
– Fields
In summary, avoid using instance fields as Optional
, why?
- Because
Optional
class are notSerializable
. - Because fields constitute part of the state of the object. As an alternative, you can create a getter accessor that returns an
Optional
.
public class MyObject { private String myField; public Optional<String> getMyField(){ return Optional.ofNullable(myField); } }
– Parameters
There is no restriction to use an Optional as a parameter, but you mind want to consider that Optional
is still an Object
and someone can send null
as a parameter of the method.
References
- https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/Optional.html
- https://dzone.com/articles/optional-in-java
- https://www.arquitecturajava.com/optional-map-y-el-concepto-de-delegacion/
- https://www.baeldung.com/java-optional