Java 8 Optional Usage and Best Practices
Learn more about Java 8's Optional and best practices on how to use it.
Join the DZone community and get the full member experience.
Join For FreeAccording to the Oracle documentation, an Optional is a container object that may or may not contain a non-null value. It was introduced in Java 8 to cure the curse of NullPointerExceptions
. In essence, Optional is a wrapper class that contains a reference to some other object. In this context, an object is just a pointer to a memory location and it can as well point to nothing. Another way to look at it is as a way to provide a type-level solution for representing optional values instead of null references.
Life Before Optional
Before Java 8, programmers would return null instead of Optional. There were a few shortcomings with this approach. One was that there wasn’t a clear way to express that null might be a special value. By contrast, returning an Optional is a clear statement in the API that there might not be a value in there. If we wanted to make sure that we won't get a null pointer exception, then we would need to do explicit null check for each reference, as shown below, and we all agree that’s a lot of boilerplate.
// Life before Optional
private void getIsoCode( User user){
if (user != null) {
Address address = user.getAddress();
if (address != null) {
Country country = address.getCountry();
if (country != null) {
String isocode = country.getIsocode();
if (isocode != null) {
isocode = isocode.toUpperCase();
}
}
}
}
}
To ease this process, let’s take a look at how we can use the Optional class instead, from creating and verifying an instance to using the different methods it provides and combining it with other methods that return the same type, the latter being where the true power of Optional lies.
Features of Optional
The optional class provides around 10 methods, which we can use for creating and using the Optional class and we are going to see how they are used below.
Creating an Optional
The are three creational methods for creating an optional instance.
1. static <T> Optional<T> empty()
Returns an empty Optional instance.
// Creating an empty optional
Optional<String> empty = Optional.empty();
Returns an empty {Optional} instance. No value is present for this Optional. Though, it may be tempting to do so, avoid testing if an object is empty by comparing with {==} against instances returned by Option.empty()
. There is no guarantee that it is a singleton. Instead, use isPresent()
.
2. static <T> Optional<T> of(T value)
Returns an Optional with the specified present non-null value.
// Creating an optional using of
String name = "java";
Optional<String> opt = Optional.of(name);
The static method of expects a non-null argument; otherwise, it will throw a nullpointer
exception. So, what if we don't know if the argument will be null or not, that's when we use ofNullable
, which is described below.
3. static <T> Optional<T> ofNullable(T value)
Returns an Optional describing the specified value, if non-null, otherwise returns an empty Optional.
// Possible null value
Optional<String> optional = Optional.ofNullable(name());
private String name(){
String name = "Java";
return (name.length() > 5) ? name : null;
}
By doing this, if we pass in a null reference, it doesn’t throw an exception but rather returns an empty Optional object:
So, those are the three methods of creating Optionals either dynamically or manually. The next set of methods is for checking value presence.
1. boolean isPresent()
Return true if there is a value present; otherwise, it's false. Meaning to returns true if the contained object is not null and false otherwise. This method is normally called first on an Optional before doing any other operations on the object.
//ispresent
Optional<String> optional1 = Optional.of("javaone");
if (optional1.isPresent()){
//Do something, normally a get
}
2. boolean isEmpty()
Return false if there is a value present; otherwise, it's true. This does the opposite of isPresent
and is only available in Java 11 and above.
//isempty
Optional<String> optional1 = Optional.of("javaone");
if (optional1.isEmpty()){
//Do something
}
3. void ifPresent(Consumer<? super T> consumer)
If a value is present, invoke the specified consumer with the value; otherwise, do nothing.
If you are new to Java 8, then you are probably wondering: What is a consumer? Well, in simple terms, a consumer is a method that accepts an argument and does not return anything. When using ifPresent
, this looks like killing two birds with one stone. We can do a value presence check and perform the intended operation with one method, as shown below.
//ifpresent
Optional<String> optional1 = Optional.of("javaone");
optional1.ifPresent(s -> System.out.println(s.length()));
The optional class provides another set of methods for getting a value in an Optional.
If a value is present in this Optional, returns the value, otherwise throws NoSuchElementException
. After all is said and done, what we want is the value that is stored in the Optional, and we can get it by simply calling get()
. However, this method throws an exception when the value is null; that's when the orElse()
method comes to the rescue.
//get
Optional<String> optional1 = Optional.of("javaone");
if (optional1.isPresent()){
String value = optional1.get();
}
Return the value if present; otherwise, return other.
The orElse()
method is used to retrieve the value wrapped inside an Optional instance. It takes one parameter that acts as a default value. The orElse()
method returns the wrapped value if it’s present and its argument, otherwise:
//orElse
String nullName = null;
String name = Optional.ofNullable(nullName).orElse("default_name");
As if that is not enough, the Optional class goes on to provide another method of getting a value even if its null called orElseGet()
.
3. TorElseGet(Supplier<? extends T> other)
Return the value if present; otherwise, invoke other and return the result of that invocation.
The orElseGet()
method is similar to orElse()
. However, instead of taking a value return if the Optional value is not present, it takes a supplier functional interface, which is invoked and returns the value of the invocation:
//orElseGet
String name = Optional.ofNullable(nullName).orElseGet(() -> "john");
So, what is the difference between orElse()
and orElseGet()
.
At first glance, it might seem as if the two methods have the same effect. However, this is not exactly the case. Let’s create some examples that highlight the similarity as well as the difference in behavior between the two.
First, let’s see how they behave when an object is empty:
String text = null;
String defaultText = Optional.ofNullable(text).orElseGet(this::getDefaultValue);
defaultText = Optional.ofNullable(text).orElse(getDefaultValue());
public String getDefaultValue() {
System.out.println("Getting Default Value");
return "Default Value";
}
In the above example, we wrap a null text inside an Optional object and we attempt to get the wrapped value using each of the two approaches. The side effect is as below:
Getting default value...
Getting default value...
The default method is called in each case. It so happens that when the wrapped value is not present, then both orElse()
and orElseGet()
work exactly the same way.
Now, let’s run another test where the value is present, and ideally, the default value should not even be created:
In this simple example, there is no significant cost to creating a default object, as the JVM knows how to deal with such. However, when a method such as default
has to make a web service call or even query a database, then the cost becomes very obvious.
Best Practices for Using Optional
Just like any other feature of a programming language, it can be used correctly or it can be abused. In order to know the best way to use the Optional class, one needs to understand the following:
1. What it Is Trying to Solve
Optional is an attempt to reduce the number of null pointer exceptions in Java systems, by adding the possibility to build more expressive APIs that account for the possibility that sometimes return values are missing.
If Optional was there since the beginning, most libraries and applications would likely deal better with missing return values, reducing the number of null pointer exceptions and the overall number of bugs in general.
2. What it IS Not Trying to Solve
Optional is not meant to be a mechanism to avoid all types of null pointers. The mandatory input parameters of methods and constructors still have to be tested for example.
Like when using null, Optional does not help with conveying the meaning of an absent value. In a similar way, null can mean many different things (value not found, etc.), so can an absent Optional value.
The caller of the method will still have to check the javadoc of the method for understanding the meaning of the absent Optional, in order to deal with it properly.
Also, in a similar way that a checked exception can be caught in an empty block, nothing prevents the caller of calling get()
and moving on.
3. When to Use It
The intended use of Optional is mainly as a return type. After obtaining an instance of this type, you can extract the value if it’s present or provide an alternate behavior if it’s not.
One very useful use case of the Optional class is combining it with streams or other methods that return an Optional value to build fluent APIs. See code snippet below
User user = users.stream().findFirst().orElse(new User("default", "1234"));
4. When Not to Use It
a) Do not use it as a field in a class as it is not serializable
If you do need to serialize an object that contains an Optional value, the Jackson library provides support for treating Optionals as ordinary objects. What this means is that Jackson treats empty objects as null and objects with a value as fields containing that value. This functionality can be found in the jackson-modules-java8 project.
b) Do not use it as a parameter for constructors and methods as it would lead to unnecessarily complicated code.
User user = new User("john@gmail.com", "1234", Optional.empty());
Final Thoughts
This is a value-based class; use of identity-sensitive operations (including reference equality (==), identity hash code, or synchronization) on instances of Optional may have unpredictable results and should be avoided.
Hope this helps! Let us know your thoughts on Optional in the comments below.
Published at DZone with permission of Hopewell Mutanda. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments