Deep dive into Optional class API in Java 8
We all as Java programmers been through the situation where in we invoke a method to get some value and then instead of directly invoking some methods on the return value, we first have to check that the return value is not null and then invoke the methods on the return value. This has been a pain point which external APIs like Guava have tried to solve. Also alternate JVM languages like Scala, Ceylon and others have this features baked right into the core APIs. In my previous post I wrote about support in one such JVM language namely Scala.
The newer version of Java i.e Java 8 introduces a new class called Optional. The Javadoc for Optional class says:
A container object which may or may not contain a non-null value. If a value is present, isPresent() will return true and get() will return the value.
In this post lets go through each of the methods present in Optional class and explain the same with an example or two.
of
Returns an Optional with the specified present non-null value.
This method is a factory method for creating instances of Optional class. Something to take note of here is that the value being passed to created the instance has to be non-null. If the value passed is null then a NullPointerException
is thrown.
//Creating an instance of Optional using the factory method. Optional<String> name = Optional.of("Sanaulla"); //This fails with a NullPointerException. Optional<String> someNull = Optional.of(null);
ofNullable
Returns an Optional describing the specified value, if non-null, otherwise returns an empty Optional.
Similar to the of
method, the only difference is that this method handles null values as well. An example:
//This represents an instance of Optional containing no value //i.e the value is 'null' Optional empty = Optional.ofNullable(null);
isPresent
Very simple to understand:
Return true if there is a value present, otherwise false.
Something like:
//isPresent method is used to check if there is any //value embedded within the Optional instance. if (name.isPresent()) { //Invoking get method returns the value present //within the Optaional instance. System.out.println(name.get());//prints Sanaulla }
get
If a value is present in this Optional, returns the value, otherwise throws NoSuchElementException.
This method is used to retrieve the value present in the Optional instance. We saw one such example above. Lets look at an example where a NoSuchElementException
is thrown:
//The below code prints: No value present try { //Invoking get method on an empty Optaional instance //throws NoSuchElementException. System.out.println(empty.get()); } catch (NoSuchElementException ex) { System.out.println(ex.getMessage()); }
ifPresent
If a value is present, invoke the specified consumer with the value, otherwise do nothing.
To understand this method you would have to understand about the Consumer class. In short Consumer is a class with single abstract method to consumer some value and perform some operation on it without returning any value. In Java 8 one can pass a lambda expression to the method expecting an implementation of Consumer interface.
The above method accepts an block of code/lambda expression to perform some operation if the value is present in the Optional instance. Something like:
//ifPresent method takes a lambda expression as a parameter. //The lambda expression can then consume the value if it is present //and perform some operation with it. name.ifPresent((value) -> { System.out.println("The length of the value is: " + value.length()); });
orElse
Return the value if present, otherwise return other.
This method either returns the value present in the Optional instance and if not it returns the value passed as a parameter to the orElse
method. Lets look at an example:
//orElse method either returns the value present in the Optional instance //or returns the message passed to the method in case the value is null. //prints: There is no value present! System.out.println(empty.orElse("There is no value present!")); //prints: Sanaulla System.out.println(name.orElse("There is some value!"));
orElseGet
This method is similar to the above method. The difference is in how the default value is obtained. In the orElse
method we pass a fixed string as the default value, but in orElseGet
method we pass an implementation of Supplier interface which has a method which is used to generate the default value. Lets look at an example:
//orElseGet is similar to orElse with a difference that instead of passing //a default value, we pass in a lambda expression which generates the default //value for us. //prints: Default Value System.out.println(empty.orElseGet(() -> "Default Value")); //prints: Sanaulla System.out.println(name.orElseGet(() -> "Default Value"));
orElseThrow
Return the contained value, if present, otherwise throw an exception to be created by the provided supplier.
Just like in the method orElseGet
we pass a Supplier interface, but in orElseThrow
method we pass a lambda expression/method reference to throw an exception when the value is not found. An example for this:
try { //orElseThrow similar to orElse method, instead of returning a default //value, this method throws an exception which is generated from //the lambda expression/method reference passed as a param to the method. empty.orElseThrow(ValueAbsentException::new); } catch (Throwable ex) { //prints: No value present in the Optional instance System.out.println(ex.getMessage()); }
And the definition for ValueAbsentException is:
class ValueAbsentException extends Throwable { public ValueAbsentException() { super(); } public ValueAbsentException(String msg) { super(msg); } @Override public String getMessage() { return "No value present in the Optional instance"; } }
map
From the documentation for map method:
If a value is present, apply the provided mapping function to it, and if the result is non-null, return an Optional describing the result. Otherwise return an empty Optional.
This method is used to apply a set of operations on the value present in the Optional instance. The set of operations are passed in the form of a lambda expression representing an implementation of the Function interface. If you are not familiar with the Function interface, then please spend some time reading my earlier blog post on the same topic here. Lets look at an example for the map
method:
//map method modifies the value present within the Optional instance //by applying the lambda expression passed as a parameter. //The return value of the lambda expression is then wrapped into another //Optional instance. Optional<String> upperName = name.map((value) -> value.toUpperCase()); System.out.println(upperName.orElse("No value found"));
flatMap
From the documentation for the flatMap
method:
If a value is present, apply the provided Optional-bearing mapping function to it, return that result, otherwise return an empty Optional. This method is similar to map(Function), but the provided mapper is one whose result is already an Optional, and if invoked, flatMap does not wrap it with an additional Optional.
This method is very similar to map
method and differs from it in the return type of the mapping function passed to it. In the case of map
method the mapping function return value can be of any type T
, where as in case of flatMap
method the return value of the mapping function can only be of type Optional
Lets look at the above example for map
method being re-written for flatMap
method:
//flatMap is exactly similar to map function, the differece being in the //return type of the lambda expression passed to the method. //In the map method, the return type of the lambda expression can be anything //but the value is wrapped within an instance of Optional class before it //is returned from the map method, but in the flatMap method the return //type of lambda expression's is always an instance of Optional. upperName = name.flatMap((value) -> Optional.of(value.toUpperCase())); System.out.println(upperName.orElse("No value found"));//prints SANAULLA
filter
This method is used to restrict the value within an Optional
instance by passing the condition to be applied on the value to the filter
method. The documentation says:
If a value is present, and the value matches the given predicate, return an Optional describing the value, otherwise return an empty Optional.
By now you must have got an idea of how to pass some block of code to the method. Yes, it is a lambda expression. In the case of this method we have to pass a lambda expression which would be an implementation of the Predicate interface. If you are not familiar with the Predicate interface, then please take some time to read this post.
Now lets look at different usages of filter
method i.e both examples of condition being satisfied and condition not being satisfied.
//filter method is used to check if the given optional value satifies //some condtion. If it satifies the condition then the same Optional instance //is returned, otherwise an empty Optional instance is returned. Optional<String> longName = name.filter((value) -> value.length() > 6); System.out.println(longName.orElse("The name is less than 6 characters"));//prints Sanaulla //Another example where the value fails the condition passed to the //filter method. Optional<String> anotherName = Optional.of("Sana"); Optional<String> shortName = anotherName.filter((value) -> value.length() > 6); //prints: The name is less than 6 characters System.out.println(shortName.orElse("The name is less than 6 characters"));
With this I have introduced you to the various methods present in the Optional
class. Let me aggregate all the above examples into one single example shown below:
public class OptionalDemo { public static void main(String[] args) { //Creating an instance of Optional //This value can also be returned from some method. Optional<String> name = Optional.of("Sanaulla"); //This represents an instance of Optional containing no value //i.e the value is 'null' Optional empty = Optional.ofNullable(null); //isPresent method is used to check if there is any //value embedded within the Optional instance. if (name.isPresent()) { //Invoking get method returns the value present //within the Optaional instance. System.out.println(name.get()); } try { //Invoking get method on an empty Optaional instance //throws NoSuchElementException. System.out.println(empty.get()); } catch (NoSuchElementException ex) { System.out.println(ex.getMessage()); } //ifPresent method takes a lambda expression as a parameter. //The lambda expression can then consume the value if it is present //and perform some operation with it. name.ifPresent((value) -> { System.out.println("The length of the value is: " + value.length()); }); //orElse method either returns the value present in the Optional instance //or returns the message passed to the method in case the value is null. System.out.println(empty.orElse("There is no value present!")); System.out.println(name.orElse("There is some value!")); //orElseGet is similar to orElse with a difference that instead of passing //a default value, we pass in a lambda expression which generates the default //value for us. System.out.println(empty.orElseGet(() -> "Default Value")); System.out.println(name.orElseGet(() -> "Default Value")); try { //orElseThrow similar to orElse method, instead of returning a default //value, this method throws an exception which is genereated from //the lambda expression/method reference passed as a param to the method. empty.orElseThrow(ValueAbsentException::new); } catch (Throwable ex) { System.out.println(ex.getMessage()); } //map method modifies the value present within the Optional instance //by applying the lambda expression passed as a parameter. //The return value of the lambda expression is then wrapped into another //Optional instance. Optional<String> upperName = name.map((value) -> value.toUpperCase()); System.out.println(upperName.orElse("No value found")); //flatMap is exactly similar to map function, the differece being in the //return type of the lambda expression passed to the method. //In the map method, the return type of the lambda expression can be anything //but the value is wrapped within an instance of Optional class before it //is returned from the map method, but in the flatMap method the return //type of lambda expression's is always an instance of Optional. upperName = name.flatMap((value) -> Optional.of(value.toUpperCase())); System.out.println(upperName.orElse("No value found")); //filter method is used to check if the given optional value satifies //some condtion. If it satifies the condition then the same Optional instance //is returned, otherwise an empty Optional instance is returned. Optional<String> longName = name.filter((value) -> value.length() > 6); System.out.println(longName.orElse("The name is less than 6 characters")); //Another example where the value fails the condition passed to the //filter method. Optional<String> anotherName = Optional.of("Sana"); Optional<String> shortName = anotherName.filter((value) -> value.length() > 6); System.out.println(shortName.orElse("The name is less than 6 characters")); } }
And the output of the above code:
Sanaulla No value present The length of the value is: 8 There is no value present! Sanaulla Default Value Sanaulla No value present in the Optional instance SANAULLA SANAULLA Sanaulla The name is less than 6 characters