Optionals: NPEs and Design Practices
If you haven't familiarized yourself with the Streams API and the Optional class, now is the time. After all, why use null references if you don't have to?
Join the DZone community and get the full member experience.
Join For FreeWhile programming, we have all faced the (in)famous NullPointerException. And I believe we all would agree that encountering NullPointerExceptions is also a pain. Just to keep the readers informed, the famous computer scientist Tony Hoare introduced null references, and even he thinks it was a million-dollar mistake. We all know it’s very easy to implement but it’s quite unpredictable as well. And that’s why developers need to be very cautious.
The Usual Way
Let’s consider three simple POJOs as follows.
public class Employee {
private Car car;
public Car getCar() {
return car;
}
}
public class Car {
private Insurance insurance;
public Insurance getInsurance() {
return insurance;
}
}
public class Insurance {
private String name;
public String getName() {
return name;
}
}
Just to give some context, an employee can own a car (not mandatory though), a car can have insurance (not necessarily), and the insurance must always have a name. But keep the following sections in mind.
Let’s say we want to get the name of the insurance by providing a person instance.
public String getInsuranceName(Employee employee) {
if (employee != null) {
Car car = employee.getCar();
if (car != null) {
Insurance insurance = car.getInsurance();
if (insurance != null) {
return insurance.getName();
}
}
}
return "UNKNOWN";
}
This is what we usually do to take preventive measures so that we don’t encounter the dreaded NullPointerException. But this also pollutes the source code and, in my opinion, it should be considered an antipattern.
Another Usual Way
Such deep nesting for null checks, as mentioned in the previous section, looks a bit obtuse. And sometimes, people do it in a different way.
public String getInsuranceName(Employee employee) {
if (employee == null) {
return "UNKNOWN";
}
Car car = employee.getCar();
if (car == null) {
return "UNKNOWN";
}
Insurance insurance = car.getInsurance();
if (insurance == null) {
return "UNKNOWN";
}
return insurance.getName();
}
This looks pretty okay to me, as it doesn’t comprise the deep nesting null checks. But still, it follows the same antipattern to check for nulls, just in a slightly different way.
Why NULL Is Not Good
- It deteriorates the readability of the source code.
- It is not semantically correct to present something without a value.
- It is against the ideology of Java, as Java hides pointers from the developers except in the situation of null references.
Alternatives to NULL
A few languages such as Scala and Groovy removed the dreaded use of null references to signify the absence of a value. Similar code can be written in Groovy in a very concise manner.
def name = employee?.car?.insurance?.name
The ? is the important part. This is known as the Safe Navigation operator in Groovy and it clearly shows much more readable code while, at the same time, removing the possibility of encountering dreaded null references.
Java’s Endeavor
Now we should ask what a Java developer can do to achieve something similar that prevents the possibility of NullPointerExceptions while maintaining readable and maintainable source code. Java's language designers chose a similar approach to that Groovy or Scala with the introduction of a new class: Optional.
Optional
public final class Optional < T > {
public static < T > Optional < T > empty() {}
public static < T > Optional < T > of(T value) {}
public static < T > Optional < T > ofNullable(T value) {}
public Tget() {}
public boolean isPresent() {}
public void ifPresent(Consumer << ? super T > consumer) {}
public Optional < T > filter(Predicate << ? super T > predicate) {}
public < U > Optional < U > map(Function << ? super T, ? extends U > mapper) {}
public < U > Optional < U > flatMap(Function << ? super T, Optional < U >> mapper) {}
public T orElse(T other) {}
public T orElseGet(Supplier << ? extends T > other) {}
public < X extends Throwable > T orElseThrow(Supplier << ? extends X > exceptionSupplier) throws X {}
}
This class is primarily used to signify the absence or presence of a value. If you believe a value can or cannot be always present, it is better to use an Optional type. In our previous example, an employee may or may not contain a car, and that’s why it is better to return Optional <Car> instead of simply returning Car.
Let’s see how we could design our previous example:
public class Employee {
private Car car;
public Optional < Car > getCar() {
return Optional.ofNullable(car);
}
}
public class Car {
private Insurance insurance;
public Optional < Insurance > getInsurance() {
return Optional.ofNullable(insurance);
}
}
public class Insurance {
private String name;
public String getName() {
return name;
}
}
I have not discussed the static factory ofNullable(..) method, but just consider it as a wrapper utility method that wraps a value irrespective of its reference.
Just by seeing the API, one could easily understand what needs to be done when an optional type is encountered. For a developer, an encounter with such optional types always signifies the possibility of the absence of a value and, hence, developers can take proper measures for this.
Optional Creation
From the class overview, we can clearly see that an Optional can be created in various ways.
- of(..): This allows the creation of an Optional instance wrapping a non-null value.
- empty(): This creates an empty Optional.
- ofNullable(..): This allows the creation of an Optional instance wrapping any value (null or non-null).
Optional Extraction and Transformation
So far, we have already seen how to create Optional instances. Now we should see how to extract the value or transform it to another.
get() returns the contained value or it throws NoSuchElementException if the Optional instance is empty.
But how should we use this?
Car car = employee.getCar();
if (employee != null) {
car = employee.getCar();
}
This is what we mostly do to evade NullPointerExceptions. Now with Java 8 Optionals, we can write the same as follows:
Optional<Car> car = employee.getCar();
if (!car.isEmpty()) {
Car car = car.get();
}
But do you consider it as an improvement over the nasty null checks?
I used to consider it as an improvement, as it hides the null pointers, but later on, I felt it polluted the source code quite a bit. But I am not against the use of returning Optional as types from methods or wrapping variables. I will discuss my reasons behind it in the following sections.
Let’s consider the previous method:
public String getInsuranceName(Employee employee) {
return employee.getCar().getInsurance().getName();
}
This is very clean code but the NullPointerException is lurking, and that’s why we need to incorporate several null reference checks (we have already seen this previously).
If we incorporate public String Optional in designing a good API, this could have been achieved in a more concise way:
public String getInsuranceName(Optional<Employee> employee) {
return employee.flatMap(Employee::getCar)
.flatMap(Car::getInsurance)
.map(Insurance::getName)
.orElse("UNKNOWN");
}
Isn’t it really nice and a cleaner approach? I know this gets confusing to some programmers who are not yet comfortable with the Java Streams API. I would strongly suggest having a quick look at Java 8 Streams to understand the beauty of Optionals.
Another example would be to get the insurance name if the name of the person starts with “P”:
public String getInsuranceName(Optional<Employee> employee) {
return employee.filter(e-> e.getName().startsWith("P"))
.flatMap(Employee::getCar)
.flatMap(Car::getInsurance)
.map(Insurance::getName)
.orElse("UNKNOWN");
}
Design Practices
Now I would like to share some ideas on designing our previously discussed POJOs in a bit of a different way.
API Design Practice 1
public class Employee {
private Optional < Car > car;
public Optional < Car > getCar() {
return car;
}
}
public class Car {
private Optional < Insurance > insurance;
public Insurance getInsurance() {
return insurance;
}
}
public class Insurance {
private String name;
public String getName() {
return name;
}
}
Here, I have declared the member variable to be of the Optional type. In my opinion, this is also very user-friendly, and users or consumers of this class can easily understand the nature of this class. In this context, an employee has a car, which is Optional, — that is, an employee may or may not have a car as well.
API Design Practice 2
public class Employee {
private Car car;
public Optional < Car > getCar() {
return Optional.ofNullable(car);
}
}
public class Car {
private Insurance insurance;
public Optional < Insurance > getInsurance() {
return Optional.ofNullable(insurance);
}
}
public class Insurance {
private String name;
public String getName() {
return name;
}
}
This is also quite intuitive, but it lacks the idea of clearly showing the absence of a member instance. To understand any system, developers always need to understand the object model first. And understanding an object model requires us to understand the domain objects. In this scenario, an employee is a domain object that has a car as if it is mandatory for an employee. But in reality, an employee may or may not have a car. We could achieve it when we retrieve its value (getCar()) and we could then notice its possibility of the absence of a contained value, as the method returns Optional.
What to Use?
It solely depends on the developer. I personally prefer the first approach, as it is clear in understanding the domain model, while the second approach has advantages for serialization. As Optional doesn’t implement Serializable, it is not serializable in our first approach. If we use DTOs, we can adapt our implementation to the second approach.
Optional in Method or Constructor Arguments
As I have mentioned previously, Optional in classes clearly shows what the consumers are supposed to do. So, if a constructor or method accepts Optional elements as arguments, it means that the argument is not mandatory.
On the other hand, we need to pay the price of polluting the codebase with Optionals. It is at the developer’s sole discretion to use it carefully. I personally prefer not to use Optional in method arguments while, if needed, we can still wrap it inside an Optional instance and perform the necessary operations on it.
Optional in Method Return Types
Java Language Architect Brian Goetz also advises returning Optional in methods if there is a possibility of returning null. We have already seen this in our API Design Practice 2.
Throw Exceptions From Methods or Return Optional
For years, Java developers followed the usual way to throwing exceptions to signify an erroneous situation in a method invocation.
public static InputStream getInputStream(final String path) {
checkNotNull(path, "Path cannot be null");
final URL url = fileSystem.getEntry(path);
InputStream xmlStream;
try {
xmlStream = url.openStream();
return xmlStream;
} catch (final IOException ex) {
throw new RuntimeException(ex);
}
}
If the consumer of this method encounters a RuntimeException, that’s due to a problem in opening a connection to the specified URL. On the other hand, we could also use Optional the following way:
public static Optional < InputStream > getInputStream(final String path) {
checkNotNull(path, "Path cannot be null");
final URL url = fileSystem.getEntry(path);
InputStream xmlStream;
try {
xmlStream = url.openStream();
return Optional.of(xmlStream);
} catch (final IOException ex) {
return Optional.empty();
}
}
I think this is very intuitive, as it clearly says that it returns an Optional instance that may or may not have a value. And that’s why my inclination is to return Optional from methods that could have such a null encounter possibility.
Optional Return Types in Private Methods
Private methods are not clearly meant to understand or analyze any significant part of a project. Hence, I think we still can make use of null checks to get rid of overly many Optionals. But if you think you still can use the method in a more clean and concise way, you can return Optional as well.
For better comprehension, I have formulated an example as follows:
private void process(final String data) {
try {
final ItemList nList = doc.getChildNodes();
for (int temp = 0; temp < nList.getLength(); temp++) {
final Node nNode = nList.item(temp);
final String key = nNode.getName();
final String value = nNode.getValue();
values.put(getAttribute(key).orElseThrow(IllegalArgumentException::new), value);
}
} catch (final Exception ex) {
logger.error("{}", ex.getMessage(), ex);
}
}
private Optional < Attribute > getAttribute(final String key) {
return Arrays
.stream(Attribute.values())
.filter(x - > x.value()
.filter(y - > y.equalsIgnoreCase(key))
.isPresent())
.findFirst();
}
public static enum Attribute {
A("Sample1"),
B("Sample2"),
C("Sample3");
private String value;
private Attribute(String value) {
this.value = value;
}
public Optional < String > value() {
return Optional.ofNullable(value);
}
}
I could have written the second method in a more usual way:
private Attribute getAttribute(final String key) {
for (final Attribute attribute: Attribute.values()) {
Optional < String > value = attribute.value();
if (value.isPresent() && value.get().equalsIgnoreCase(key)) {
return attribute;
}
}
throw new IllegalArgumentException();
}
Optional Return Type in Private Methods Returning Collections or Any of Its Subtypes
As a first example, consider the code you need to implement a method to list files from a specified path in Java:
public static List<String> listFiles(String file) {
List<String> files;
try {
files = Files.list(Paths.get(path));
} catch (IOException e) {
files = Arrays.asList("Could not list");
}
return files;
}
We could achieve more concise code as follows:
public static List<String> listFiles(String path) {
return Files.list(Paths.get(path))
.filter(Files::isRegularFile)
.collect(toList());
}
Notice that the return type in the concise method still remains List instead of Optional. It is preferable to follow the usual practice of returning an empty list instead of using Optional.
It is apparent that the Stream ways of using Optional are more concise. Optional is a utility data container that helps developers to get rid of null references. In addition, it provides lots of useful methods that ease the programmer’s work. But Optional can be misused heavily and can pollute the codebase if the developer is not well-aware of the primary use of Optionals. That’s why I strongly suggest everyone have a go at the Stream-oriented methods in Optionals that help developers to write concise and maintainable code.
Published at DZone with permission of Amit Kumar Mondal, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Trending
-
Effective Java Collection Framework: Best Practices and Tips
-
Micro Frontends on Monorepo With Remote State Management
-
Integration Testing Tutorial: A Comprehensive Guide With Examples And Best Practices
-
How Agile Works at Tesla [Video]
Comments