Java 8 Optional
Optional is a new class added in java 8 in java.util
package.
This article will offer an in-depth explanation about java Optional
class and its objects.
In a single line,
Optional
is designed to deal with NullPointerException in java in an elegant manner.
You might be familiar with null
references which lead to the most commonly found NullPointerException
as shown below.
String str = null; if(str.equals("codippa")) { // do something }
Line 2 in above example will throw a java.lang.NullPointerException
since we are trying to invoke a method on null
reference.
Optional
class has been added to address this problem.
You might say that NullPointerException
can be avoided by a simple null
check such as in code below.
String str = null; if(str != null && str.equals("codippa")) { // do something }
Then why a separate class for this.
You are right but have a look at below code.
String country = student.getStudentDetails().getAddress().getCountry().toLowerCase(); switch(country) { case "India": // do something case "USA": // do something // other countries }
This example has chances of NullPointerException
at multiple places if any of the objects student, studentDetails, address or country are null
.
Now you might be willing to put null
checks as before. But, doing that would transform above code to
String country = null; if(student != null) { StudentDetails studentDetails = student.getStudentDetails(); if(studentDetails() != null) { Address address = studentDetails().getAddress(); if(address() != null) { Country studentCountry = address.getCountry(); if(studentCountry != null) { country = studentCountry.toLowerCase(); } } } } // more code
This code is pretty difficult to read as you can see and is also hard to maintain and modify.
Optional
is created to solve this problem and we shall re-visit it after learning Optional
.
What is Optional
Optional
is a container or a wrapper over an object. It might contain a value(or object) or it might not.
Optional
can contain any object and hence it is declared with a generic type such as Optional<String>
, Optional<Cat>
etc., and it looks as below.
With Optional
, you can choose to get its value only if it is present, return a default value if the object that it holds is not present or null
etc.
Thus, it solves the problem of NullPointerPointerException
making the code more readable as compared to null
checks.
We will see how the above example can be modified with Optional
objects after learning it in depth.
An Optional object can not be created using its constructor since its constructor is private.
Optional object can only be created using its static methods as explained below.
1. Using of() method
An
Optional
can be created by calling its static of()
method supplying it the object that the Optional
will hold or the object wrapped by Optional
.Example,
Student student = new Student(); Optional<Student> optional = Optional.of(student);
If the object provided to of is null
, then it will raise a NullPointerException
.
If there is a chance of the object being null
, use the method discussed next.
2. Using ofNullable() method
This is the second way of creating an Optional
object.
ofNullable()
is also a static
method which accepts an object as argument and creates an Optional
that contains this object.
If the object is null
, it creates and returns an empty Optional
object. Example,
Student student = new Student(); Optional<Student> optional = Optional.ofNullable(student);
3. Creating empty Optional
It is also possible to create an Optional
object with empty value using its static empty
method as shown below.
Optional<Student> optional = Optional.empty(student);
Optional
returned by empty can be assigned to any reference type meaning it could be Optional<String>
, Optional<Shape>
, Optional<Object>
and so on.
Value of Optional
As stated before, Optional
is a wrapper around an object but how do you access the underlying object.
Optional
provides a get method which returns the object which it wraps.
Type of this object is the same as the type of Optional
. Example,
String color = "red"; Optional<String> optional = Optional.of(color); String back = optional.get(); System.out.print("Color is : " + back);
which outputs
Color is : red
Check Optional value
If an Optional
object does not contain a value, that is, it is an empty Optional
created using its empty()
or ofNullable()
methods and you try to access its value by using get()
, you will face an error
Exception in thread “main” java.util.NoSuchElementException: No value present
To prevent this error, it is better to check if Optional
contains a value before accessing it using its isPresent()
or isEmpty()
methods.
isPresent()
This method returns true
if Optional
contains a value and false
otherwise. Example,
// create empty Optional value Optional<String> optional = Optional.empty(); System.out.print("Optional contains a value? " + optional.isPresent());
which outputs
Optional contains a value? false
isEmpty()
This methods works opposite to isPresent()
and returns true
if the Optional
object does not contain a value, false
otherwise. Example,
// create empty Optional value Optional<String> optional = Optional.empty(); System.out.print("Optional contains a value? " + optional.isEmpty());
This outputs
Optional contains a value? true
Action on value present
In the last section we saw how to check if Optional
contains a value.
If you want to perform some action if Optional
contains a value, then you might think of following lines.
Optional<String> o = Optional.empty(); // check if optional contains a value if(o.isPresent()) { System.out.print(o.get()); }
However, Optional
provides a shortcut method that does not require an if
condition.
It is by using ifPresent()
method of Optional
.
This method accepts an argument of type java.util.function.Consumer
which is a functional interface, hence you can directly supply its implementation as a Lambda expression.
Example of ifPresent()
is given below.
Optional<String> o = Optional.of("Learning optional"); // print value of optional if non-empty o.ifPresent((e) -> System.out.println(o.get()));
This prints
Learning optional
Above example showed what action to take if
Optional
has a value but what if it does not.Optional provides methods to perform some action if it is empty.
These methods are
1. orElse()
This method accepts a value as argument and returns this value if the
Optional
is empty.Type of this argument must be same as the type of
Optional
.Example,
// simple pizza Pizza plainPizza = new Pizza(); plainPizza.setDescription("Pizza without any toppings"); // create empty Optional Optional<Pizza> empty = Optional.empty(); // get pizza from optional Pizza p = empty.orElse(plainPizza); System.out.print(p.getDescription())
This example creates an empty Optional
and then invokes its orElse()
method with a default object as argument. Output of this code is
Pizza without any toppings
which shows that the object returned by orElse()
is the same as its argument.
2. orElseGet()
This method is also similar to orElse()
and returns a value if the Optional
is empty.
It accepts an argument of type java.util.function.Supplier
and thus it returns the value returned by the supplier.
Example of orElseGet()
is given ahead
// create empty optional Optional<String> empty = Optional.empty(); String s = empty.orElseGet(() -> "Optional is empty"); System.out.println(s);
which prints
Optional is empty
Note that the argument to orElseGet()
method is a Lambda expression since Supplier
is a Functional interface.
If above syntax seems confusing, then go through the below code which breaks the call to orElseGet()
in separate lines.
// define a supplier Supplier<String> supplier = () -> "Optional is empty"; String s = empty.orElseGet(supplier);
Difference between orElse() & orElseGet()
1. A visible difference between the two methods is that orElse()
accepts an argument of the same type as the Optional
object while orElseGet()
method accepts an argument of type java.util.function.Supplier
.
2. Another difference is orElse()
simply returns the argument supplied to it while orElseGet()
invokes a Supplier
method to retrieve the result.
3. But there is another difference behind the scenes and this difference can be demonstrated in the below example.
private void getData() { System.out.println("Fetching data"); } // create non-empty optional Optional<String> o = Optional.of("Learning optional"); System.out.println("------------ orElse ------------"); // using orElse String s = o.orElse(getData()); System.out.println(s); System.out.println("\n------------orElseGet-------------"); // using orElseGet s = o.orElseGet(() -> getData()); System.out.println(s);
Instead of directly supplying values to orElse()
and orElseGet()
, we are calling a method that returns a value.
This code when executed produces following output
———— orElse ————
Fetching data
Learning optional
————orElseGet————-
Learning optional
From this output, it is evident that even though the Optional
is not empty,
orElse()
still executes the method while orElseGet()
executes it only when the Optional
is empty.
This is considerable when there are network or database calls involved for fetching data.
3. or()
This method was added in java 9. If the Optional
contains a value, then it returns the same Optional
otherwise it returns another default Optional
object.
or()
expects and argument of type java.util.function.Supplier
. The default Optional
returned is provided by this argument Supplier
function.
Example,
String s = "optional value"; // create an optional Optional<String> o = Optional.ofNullable(s); // get Optional with or Optional<String> or = o.or(()-> Optional.of("Default")); System.out.println("Optional value:: " + or.get()); // create an optional with null value Optional<String> empty = Optional.ofNullable(null); // get Optional with or Optional<String> defaultOptional = empty.or(()-> Optional.of("Default")); System.out.println("Optional value:: " + defaultOptional.get());
This code outputs
Optional value:: Optional value
Optional value:: Default
When the Optional
contains a value, then or returns the same Optional
.
But when it is empty, then the Optional
supplied by the argument function of or is returned.
Till now we saw how
Optional
can return a default value if it is empty. But sometimes empty Optional
should be considered as an error.
For handling such situations, it is also possible to throw an error when the Optional
is empty.
Following are the methods for this
1. orElseThrow(Supplier)
This method accepts an argument of type Supplier interface and throws an exception that is returned by this Supplier
function.
orElseThrow()
will throw an exception only when the Optional
is empty. Example,
// create empty optional Optional<String> o = Optonal.ofNullable(null); // throw exception when optional does not have any value o.orElseThrow(() -> new IllegalArgumentException("Empty Optional"));
Above code when executed produces following result.
Exception in thread “main” java.lang.IllegalArgumentException: Empty Optional
at com.codippa.demo.DemoApplication.lambda$0(DemoApplication.java:22)
at java.base/java.util.Optional.orElseThrow(Optional.java:408)
at com.codippa.demo.DemoApplication.main(DemoApplication.java:22)
2. orElseThrow()
This method is similar to orElseThrow()
discussed above but it does not accept any argument and throws a java.util.NoSuchElementException
by default with an error message “No value present”, if the Optional
is empty.
Example,
// create empty optional Optional<String> o = Optonal.ofNullable(null); // throw exception when optional does not have any value o.orElseThrow();
This will result in
Exception in thread “main” java.util.NoSuchElementException: No value present
at java.base/java.util.Optional.orElseThrow(Optional.java:382)
at com.codippa.demo.DemoApplication.main(DemoApplication.java:22)
This method was added in java 10.
Filtering data
There may be condition when you want to return a result only when the value meets a certain criteria.
Optional
provides a filter method which accepts a Predicate representing a condition to match and returns the Optional
object if the value matched the predicate(or condition) else returns an empty Optional
.
Example,
Laptop apple = new Laptop("Apple"); Optional<Laptop> laptop = Optional.of(apple); // check if Optional<Laptop> o = laptop.filter((l) -> "Apple".equals(l.getBrand())); System.out.println(o.orElseThrow());
Above example checks the laptop brand and filters it to return an empty Optional
if the brand is other than Apple.
Note that the filter()
method will return an empty Optional
if the supplied Predicate
returns false
, that is, the condition is not matched.
orElseThrow
throws an exception if the returned Optional
is empty.
Transforming Optional
It is possible to convert an Optional
of some type to an Optional of a different type using map
and flatMap
methods.
map()
map()
accepts an argument of type java.util.function.Function
which is a Functional interface with a single method that accepts an argument and returns a value.
map()
argument function is also called mapper function.
Below example of map()
method converts an Optional<Laptop>
to an Optional<String>
// create an object Laptop apple = new Laptop("Apple"); // create Optional object Optional<Laptop> laptop = Optional.of(apple); // map laptop Optional to a string Optional Optional<String> strOptional = laptop.map(l -> l.getBrand()); // get Optional value String brand = strOptional.get(); System.out.println(brand); //prints Apple
Following are some important points regarding the map method in above example.
In the statement map(l -> l.getBrand())
, map
is supplied a Lambda expression with a single argument and that returns a value.
map()
returns an Optional
with its type modified to the return type of Lambda expression supplied as argument to map()
.
Thus, in above example, map()
returns an Optional
of type String.
Since map()
returns an Optional
, it is possible to chain other Optional
methods after map()
.
Example,
String brand = laptop.map(l -> l.getBrand()).map(s -> s.toUpperCase()).get(); System.out.println(brand); //prints APPLE
Note that the type of value to second map()
method is a String which is the type of Optional
returned by the first map()
method.
If the argument function returns null
, then map()
returns an empty Optional
.
flatMap()
Another method to transform an Optional
object is using flatMap()
method.
There are two differences between map()
and flatMap()
method.
1. Argument of flatMap()
expects a function whose return type is an Optional
object while that of map()
method expects a function whose return type is a value such as a String in the example above.
2. map()
returns an Optional
wrapping the value of some type while flatMap()
returns the value directly, it does not return an Optional
.
3. flatMap()
returns an Optional
if its argument Optional
is empty and a value if the argument Optional
is non-empty while map()
returns an Optional
in both cases.
Example of flatMap()
is given below.
Optional<Laptop> laptop = Optional.of(apple); String result = laptop.flatMap(l -> l.getBrandOptional()).orElseThrow(); System.out.println(result); // prints Apple
In this example, the method getBrandOptional()
returns an Optional<String>
which wraps the brand value and thus flatMap()
returns the value of brand directly.
How Optional avoids NullPointerException
At the start of this article, following problem stating NullPointerException
was discussed.
String country = student.getStudentDetails().getAddress().getCountry().toLowerCase();
For avoiding it, multiple null
checks were required or the solution was using Optional
.
Now after learning about Optional
, you must be thinking how does Optional avoids NullPointerException
.
Modify all the classes used in this example as below.
class Student{ StudentDetails studentDetails; public Optional<StudentDetails> getStudentDetails() { return Optional.ofNullable(studentDetails); } } class StudentDetails { Address address; public Optional<Address> getAddress(){ return Optional.ofNullable(address); } } class Address { private Country country; public Optional<Country> getCountry(){ return Optional.ofNullable(country); } } class Country { private String country; public Optional<String> getCountry(){ return Optional.ofNullable(country); } }
As you can see, the getter method now returns an Optional
wrapping the required object instead of the object directly.
For getting the country, the statement will be written as
Student st = null; String country = Optional.ofNullable(st). flatMap(s -> s.getStudentDetails()). flatMap(sd -> sd.getAddress()). flatMap(a -> a.getCountry()). flatMap(c -> c.getCountry()). orElse("Country not found");
Each call to flatMap()
returns an Optional
object and finally, if the Optional
returned by getCountry()
is empty, then an error message is returned but no NullPointerException
.
That is all on Optional class added in java 8.
Hope the article was useful.