avatarClean Code Clean Life

Summary

The article discusses the use of Java 8's Optional class to handle null checks more elegantly and avoid the common NullPointerException (NPE), suggesting that senior developers should prefer this approach over traditional if-else statements.

Abstract

The article begins by addressing the prevalent issue of NullPointerException (NPE) in software development, particularly when dealing with potentially null objects in a class diagram. It proposes the use of Java 8's Optional class as a superior alternative to the conventional if-else null checks. The Optional class provides methods such as of, ofNullable, empty, orElse, orElseGet, orElseThrow, map, and flatMap to streamline the handling of null values without resorting to verbose and error-prone conditional logic. The author emphasizes the importance of choosing the right method for the context, such as using ofNullable when a null value should not throw an exception, and of when a null should be reported immediately. The article also highlights the performance differences between orElse and orElseGet, and provides code examples to demonstrate how to replace nested if-else blocks with cleaner, more maintainable Optional-based code.

Opinions

  • The author suggests that the use of Optional is a more sophisticated approach for null checks, indicating that it is a practice expected of experienced developers.
  • It is implied that the Optional constructor should not be used directly due to its private access, and instead, the provided static methods should be utilized.
  • The author expresses a clear preference for ofNullable over of in most cases, except when immediate reporting of null values via an exception is desired.
  • The article points out a common misunderstanding regarding orElse and orElseGet, emphasizing that orElse will execute the provided method regardless of the presence of a value, whereas orElseGet will only invoke the method if the value is absent.
  • The author plans to delve deeper into the nuances of orElse and orElseGet in a subsequent blog post, indicating an intention to provide further insights on the topic.
  • The article concludes with practical examples that illustrate how to "kill" if-else statements by using Optional, showcasing the author's belief in the effectiveness and simplicity of this approach.

A New Way to Check for Null — Say Goodbye to if-else — -part 1

At the beginning of the article, let’s address the issue of NPE, which stands for NullPointerException, a problem commonly encountered in development. Suppose we have two classes, and their UML class diagram is as shown below:

In this situation, if we have a piece of code like the following, you will get NPE.

user.getAddress().getProvince();

We can use if-else to resolve this.

if(user!=null){
Address address = user.getAddress();
if(address!=null){
String province = address.getProvince();
}
}

However as a senior developer, you SHOULD avoid this kind of ugly code. We can take advantage of Java8's Optional class to optimise this block.

1、 Optional(T value),empty(),of(T value),ofNullable(T value)

First, let’s clarify that the ‘Optional(T value)’ constructor has private access and cannot be called externally.

The source code of `Optional(T value)` looks like this:

private Optional() {
this.value = null;
}

The other three functions are public and meant to be used by us.

The source code of `of(T value)` looks like this:

public static <T> Optional<T> of(T value) {
return new Optional<>(value);
}

We can reach two conclusions:

  1. An ‘Optional’ object constructed via the ‘of(T value)’ function will still result in a NullPointerException if the ‘value’ is null.
  2. When the ‘value’ is not null, you can successfully create an ‘Optional’ object.

in addition to that, in the Optional class, we have a null object

public final class Optional<T> {
//....
private static final Optional<?> EMPTY = new Optional<>();
private Optional() {
this.value = null;
}
//...
public static<T> Optional<T> empty() {
@SuppressWarnings("unchecked")
Optional<T> t = (Optional<T>) EMPTY;
return t;
}
}

So the method empty() is used to return then EMPTY object.

What does ofNullable(T value) looks like?

public static <T> Optional<T> ofNullable(T value) {
return value == null ? empty() : of(value);
}

The key difference between ‘of(T value)’ and ‘ofNullable(T value)’ is that when the ‘value’ is null, ‘of(T value)’ will throw a NullPointerException, while ‘ofNullable(T value)’ won’t throw an exception but instead returns an EMPTY object.

Does this mean we should always use ‘ofNullable’ exclusively in our projects and avoid ‘of’? Not necessarily. Everything has its purpose. If during runtime, you don’t want to hide the NullPointerException but want it to be reported immediately, in such cases, you should use ‘of’.

2、orElse(T other),orElseGet(Supplier other) and orElseThrow(Supplier exceptionSupplier)

The three functions can be grouped together for easy recall, as they are all called when the ‘value’ passed into the constructor is null. The usage of ‘orElse’ and ‘orElseGet’ is as follows, which is akin to providing a default value when the ‘value’ is null:

@Test
public void test() {
User user = null;
user = Optional.ofNullable(user).orElse(createUser());
user = Optional.ofNullable(user).orElseGet(() -> createUser());
}
public User createUser(){
User user = new User();
user.setName("John");
return user;
}

The difference between these two functions is that when the ‘user’ value is not null, ‘orElse’ will still execute the ‘createUser()’ method, whereas ‘orElseGet’ will not execute the ‘createUser()’ method. (While writing this blog, I found this is an interesting topic, I will discuss this in my NEXT blog.)

As for ‘orElseThrow,’ it means that when the ‘value’ is null, it will directly throw an exception.

User user = null;
Optional.ofNullable(user).orElseThrow(()->new Exception("No such user"));

3、map(Function mapper) and flatMap(Function> mapper)

if the structure of User looks like this:

public class User {
private String name;
public String getName() {
return name;
}
}

if you want to get the name, then you should use the following code piece:

String city = Optional.ofNullable(user).map(u-> u.getName()).get();

For flatMap if the structure of User looks like this

public class User {
private String name;
public Optional<String> getName() {
return Optional.ofNullable(name);
}
}

if you want to get the name, then you should use the following code piece:

String city = Optional.ofNullable(user).flatMap(u-> u.getName()).get();

Let me show some samples to “Kill” if-else by using Optional

Sample1:

public String getCity(User user) throws Exception{
if(user!=null){
if(user.getAddress()!=null){
Address address = user.getAddress();
if(address.getCity()!=null){
return address.getCity();
}
}
}
throw new Excpetion("some exception raised here");
}

Java8+ approach

public String getCity(User user) throws Exception{
return Optional.ofNullable(user)
.map(u-> u.getAddress())
.map(a->a.getCity())
.orElseThrow(()->new Exception("some exception raised here"));
}

Sample2:

public User getUser(User user) throws Exception{
if(user!=null){
String name = user.getName();
if("John".equals(name)){
return user;
}
}else{
user = new User();
user.setName("John");
return user;
}
}

Java8+ approach

public User getUser(User user) {
return Optional.ofNullable(user)
.filter(u->"John".equals(u.getName()))
.orElseGet(()-> {
User user1 = new User();
user1.setName("John");
return user1;
});
}
Java
Java17
Nullpointerexception
Optional Chaining
Recommended from ReadMedium