1. Quick introduction

Null pointer exception, only you have written business system, must be familiar with it. It is a runtime error, caused by a common logic lax, lazy code style. Its causes are simple to understand, but avoiding it is not easy. Here are some of the best practices I’ve documented that have helped me avoid null-pointer exceptions while also indirectly improving my code quality and productivity. Some of this is from books I’ve read on Kindle, some from StackOverflow advice, and some from my own practice. I hope it will give you some inspiration.

2. Problem definition

Consider a business system that maintains the Execution of the most recent Execution record in a Task, which maintains the Execution Result of that Execution. Here is the interface definition:


public interface Task {
    Execution getExecution(a);
}

public interface Execution {
    Result getResult(a);
}

public interface Result {}Copy the code

Now that you have obtained the Task from the database, you need to obtain the execution result of this time. If there is a result, go to the next step, otherwise skip. We often write like this:

Task task = queryTaskFromDB();
if(task ! =null) {
    if(task.getExecution() ! =null) {
        if(task.getExecution().getResult() ! =null) { doSomethingOnResult(task.getExecution().getResult()); }}}Copy the code

In order to be logically rigorous, we have to nullate every CURD R gets, which makes the noodle code very verbose.

In real development, it is not easy to nullify each acquired class in such meticulous detail. Many times the definition in the business code is not clear whether Task::getExecution can be returned as null: if it is null or NoSuchElementException? This often varies from implementation to implementation. This is also the unfriendly side of Java because it makes it difficult to fully implement interface-oriented programming.

Some common best practices

3.1 Suggestion #0 Try modifying your interface with Optional

Take a simple business system in 2.1 as an example. We tried to modify the Task and Execution interface definitions with Optional:


public interface Task {
    Optional<Execution> getExecutionNullable(a);
}

public interface Execution {
    Optional<Result> getResultNullable(a);
}

public interface Result {}Copy the code

Optional indicates that the result returned by this method may exist or be null, if it does, it contains an object of a given type.

After the interface has been modified, what if you want to query a Task from the database and get its execution results? Look at the code at 👇 below

Optional<Task> task = queryTaskFromDBNullable();
task
    .flatMap(Task::getExecutionNullable)
    .flatMap(Execution::getResultNullable)
    .ifPresent(this::doSomethingOnResult);
Copy the code

The flatMap method results vary depending on the Optional content:

  • If Optional has a value, it takes the value and assigns it to the anonymous function
  • If Optional has no value, return oneOptional.empty()

By doing this, you can avoid tedious “noodle code”. 🙂

3.2 Other development suggestions

Our business system can avoid Null Pointer Check by modifying the interface, but what about legacy systems? What about third-party libraries? This is where code style comes in.

3.2.1 Suggestion #1 Use String. ValueOf instead of toString

Object obj = null;
obj.toString(); // Ops, error
Copy the code

ToString () is a very common method in development, but nullpointer exceptions occur if obj is null. It is better practice to use the static factory method valueOf to ensure toString always succeeds:

Object obj = null;
String.valueOf(obj); // Return "null" if null
Copy the code

The key trick here is that some of the method logic exists in some static factory methods, but using static factory methods ensures that this time the operation is completely functional without worrying that the object on which the method is located does not exist.

3.2.2 Recommendation #2 Add final for variables that do not change after initialization

String prompt = null;
int i = 50;
if (i < 50) {
    prompt = "too small";
} else if (i > 50) {
    prompt = "too large";
}
prompt.toString(); // Ops, error
Copy the code

Adding final forces you to initialize

final String prompt;
int i = 50;
if (i < 50) {
    prompt = "too small";
} else if (i > 50) {
    prompt = "too large";
} else {
    prompt = "you get it";
}
prompt.toString();
Copy the code

3.2.3 It is recommended that #3 equals be first and variables last

String var = null;
if (var.equals("test")) {
    doSomething(); // Ops, error
}
Copy the code

This will always be true if you write it this way.

String var = null;
if ("test".equals(var)) {
    doSomething(); 
}
Copy the code

Another benefit of this is that == is wrongly written as =. But as more and more people start using Intellij, the benefits of writing this way are gone.

4. To summarize

Above I described some of my own practices to circumvent or optimize null pointer checking. I hope you found this interesting. Some of them are not familiar with Optional, flatmap and Monad. I will update some articles to talk about my own practice and understanding.