The most common and frequent exception which many Java developers encounter is NullPointerException. Though this is very common exception and the fix is also straightforward for most of the developers. They simply put null check or nested null checks in order to avoid happening this exception. This actually increases the number of indentation level in your code and also reduces the readability too. Even if you avoid happening this exception in this way, the complete flow might not be consistence with all client codes. If you fix this exception with simple null check, you need to make sure that the complete flow executes correctly as per business requirement without producing strange results from the application.
And also, there are many situations where, you add this null check only after happening the NullPointerException at least a once only. Yes, some developers proactively check the presence and absence of a value of variable before it is accessed and write the complete flow which handles the absence of the value.
Java 8 introduces java.util.Optional<T> to represent the absence of a value in a particular variable or a field of any type in more precise and safer manner. This enforces developers to specifically focus on the absence of the value of their reference, a variable or return type of a method. So defining a method to accept an optional or a method to return an optional indicates that the value of that variable may not be presence. There is a possibility of absence of the value. Your peer developers or in future, if someone is going to modify the code or use those methods by different client code, they will know that the value of the reference can be absence and should write the client code according to that.
Assume the following are two methods of a data access class for Employee entity.
public Employee findEmployeeByName(String name) {
Employee emp = null; // code get employee from DB or somewhere.
return emp;
}
public Optional<Employee> findEmployeeOptionalByName(String name) {
Employee emp = null; // code to get employee from DB or somewhere
return Optional.ofNullable(emp);
}
Both method is to find an employee for a given employee name. Let's say both scenario, system could not find an employee for the given name and both method returns value absence reference (null). The first method returns null. But the second method, in stead of returning null, it returns an empty optional which indicates all clients codes that, the return value in the returned variable may or may not presence and write your client code according to that. Let's look two client code for both above method.
//Client code1
Employee emp = employeeRepository.findEmployeeByName("David");
System.out.print(emp.getName());
//Client code2
Optional<Employee> employeeOptional = employeeRepository.findEmployeeOptionalByName("David");
System.out.println(employeeOptional.get().getName());
In the above code, first client code end with NullPointerException and the second client code ends with NoSuchElementException. Off-course, in the first client code, developers may directly access 'emp' object without checking the absence of the value which results NullPointerException and then later on, they will add null check before accessing 'emp' object. This is the old practice that most developers were doing.
But, how about the second client code2 which is also not the best way to unwrap an optional. The 'findEmployeeOptionalByName()' method returns an Optional of Employee which indicates that the value may not be present. The developers should write the client code to handle the absent of the value in this case. It is a developer's responsibility and good practice too.
How, optional is unwrapped in the above client code2 is not a best way of doing. If particular method returns an optional means, developers need to specifically focus on it and write the code appropriately. The above code does not leverage the purpose of Optional.
There are several ways to unwrap an Optional. Using get() method of Optional is not a good idea every time, unless you know the the presence and absence of the value of the optional. For empty optional, this will return NoSuchElementException exception.
The optional's ifPresent() method and also for default values, orElse() method are good choices in order to unwrap an optional and get the value. Look at the following code.
//unwrap optional with isPresent() method
if(employeeOptional.isPresent()) {
System.out.println(employeeOptional.get().getName());
}
//unwrap optional with orElse() method
String empName = employeeOptional.map(Employee::getName).orElse("Unknown");
System.out.print(empName);
As in the above second approach, you can apply map() method to an optional in order to transform optional into a different type. In this case, Employee optional to a string. If optional is empty, it will execute default orElse() method.
Further you have orElseGet() method which accepts a supplier than using orElse(). The orElseGet() is more efficient than orElse(), because the code within the supplier argument will be executed only if optional value is not present. Let's say, you have to do some expensive thing, if optional value is not presence, orElseGet() method is best suited.
String empName = employeeOptional.map(Employee::getName).orElseGet(() -> "Unknnow");
System.out.println(empName);
If you want to throw an exception, in case of optional value is absence, you can use orElseThrow() method as follows. The advantage of using this method is, you can throw any exception type that you preferred.
String empName = employeeOptional.map(Employee::getName).orElseThrow(() -> new RuntimeException("Unable to find employee"));
System.out.print(empName);
You can apply map(), flatMap() and filter() methods to an optional similar as stream API.