\

Write at the front ****

In my several years of development experience, I have often seen null value judgments everywhere in projects, which can be confusing and probably have nothing to do with the current business logic. But it can give you a headache.

Sometimes, even worse, the system will throw null-pointer exceptions because of these null-value situations, causing business system problems.

This article summarizes several methods of dealing with null values, hoping to be helpful to readers.


A null value in the service ****

scenario

There is a UserSearchService that provides the user with the ability to query:

public interface UserSearchService{
  List<User> listUser();
  User get(Integer id);
}
Copy the code

The problem on site

The level of abstraction is particularly important for object-oriented languages. Interface abstraction, in particular, is a big part of design and development, and we want to program as much as possible toward interfaces when we develop.

From the interface method described above, it can be inferred that it may have two meanings:

  • listUser(): Queries the user list
  • get(Integerid): Queries a single user

In all development, THE TDD pattern favored by XP guides our definition of interfaces well, so we use TDD as the “enabler” for developing code.

For the above interfaces, we found potential problems when using TDD for test case first:

  • listUser()If there is no data, does it return an empty collection or null?
  • get(Integerid)If there is no object, do I throw an exception or return NULL?

Dig deeper into listUser

Let’s talk about it first

listUser()
Copy the code

This interface is often seen as follows:

public List<User> listUser(){
    List<User> userList = userListRepostity.selectByExample(new UserExample());
    if(CollectionUtils.isEmpty(userList)){// Spring Util utility class
      return null;
    }
    return userList;
}
Copy the code

This code returns NULL. From my years of development experience, it is best not to return null for collections like this, because if null is returned, it will cause a lot of trouble for the caller. You will be putting the call risk in the hands of the caller.

If the caller is a prudent person, he will make a null conditional. If he is not cautious, or if he is an enthusiast for interface programming (which is the right direction, of course), he will call the interface as he understands it, without a null condition, which is very dangerous, because it is very likely to have a null-pointer exception!

Based on this, we optimized it:

public List<User> listUser(){
    List<User> userList = userListRepostity.selectByExample(new UserExample());
    if(CollectionUtils.isEmpty(userList)){
      return Lists.newArrayList(); // The method provided by the Guava class library
    }
    return userList;
}
Copy the code

For the interface (ListlistUser()), it must return a List even if there is no data (there are no elements in the collection);

With the above changes, we have successfully avoided the possibility of null-pointer exceptions, which is safer to write!

Delve into the GET method

For the interface

User get(Integer id)
Copy the code

What you can see is that if I give you an ID, it will definitely give me back User. But that’s probably not the case.

I’ve seen implementations:

public User get(Integer id){
  return userRepository.selectByPrimaryKey(id);// Get the entity object directly from the database by id
}
Copy the code

I’m sure many of you will write the same.

The code will probably return null! But we can’t tell through the interface!

This is a very dangerous thing. Especially for the caller!

My advice is to add documentation when the interface is explicit, such as using the @exception annotation for exceptions:

public interface UserSearchService{
  /** * Obtain user information based on user ID * @param ID User ID * @return user entity * @exception UserNotFoundException */
  User get(Integer id);
}
Copy the code

After we annotated the interface definition, the caller could see that if the interface was called, it would most likely throw an exception such as “UserNotFoundException”.

This way you can see the definition of the interface when the caller calls it, but this way is “weak prompt”!

If the caller ignores the comment, there is a risk to the business system that could lead to 100 million!

In addition to the above “weak hint” approach, there is also the possibility that the return value is null. So what do you do?

I think we need to add an interface to describe this scenario.

Introduce JDK8’s Optional, or use Guava’s Optional. Look at the following definition:

public interface UserSearchService{
  @param ID User ID * @return User entity. This entity may be the default value */
  Optional<User> getOptional(Integer id);
}
Copy the code

Optional has two meanings: there is or default.

So by reading the getOptional() interface, we can quickly understand the intent of the return value, which is actually what we want to see, it removes ambiguity.

Its implementation can be written as:

public Optional<User> getOptional(Integer id){
  return Optional.ofNullable(userRepository.selectByPrimaryKey(id));
}
Copy the code

Deep into the reference

From all the interface descriptions above, can you be sure that the input id is mandatory? I think the answer is: not sure. Unless noted in the interface’s documentation.

So how do you constrain the input parameters?

Two methods are recommended:

  • constraint
  • Documentation constraints (weak hints)

1. Mandatory constraints, we can use JSR 303 to declare strict constraints:

public interface UserSearchService{
  /** * Obtain user information based on user ID * @param ID User ID * @return user entity * @exception UserNotFoundException */
  User get(@NotNull Integer id);
  @param ID User ID * @return User entity. This entity may be the default value */
  Optional<User> getOptional(@NotNull Integer id);
}
Copy the code

Of course, this needs to be validated with AOP’s operations, but Spring already provides a good integration solution that I won’t go into here.

2. Documentation constraints

In many cases, we encounter legacy code, for which there is little chance of a holistic overhaul.

We prefer to read the implementation of the interface to explain the interface.

The JSR 305 specification, gives us a description interface into the way of a refs (need to import library com. Google. Code. Findbugs: jsr305) :

Note @nullable @nonNULL @checkForNULL for interface description. Such as:

public interface UserSearchService{
  /** * Obtain user information based on user ID * @param ID User ID * @return user entity * @exception UserNotFoundException */
  @CheckForNull
  User get(@NonNull Integer id);
  @param ID User ID * @return User entity. This entity may be the default value */
  Optional<User> getOptional(@NonNull Integer id);
}
Copy the code

summary

Empty collection returns,Optional, JSR 303, and JSR 305 can make our code more readable and less error-prone!

  • Empty collection return value: If there is a collection that returns a value like this, always return an empty collection, not null, unless you really have a reason to do so
  • Optional: If your code is JDK8, import it! If not, use Guava’s Optional, or update the JDK version! It greatly increases the readability of the interface!
  • JSR 303: If a new project is under development, try adding this! Must be a great feeling!
  • JSR 305: If you have an old project in your hands, you can try to add this kind of documentation to help you with later refactoring, or new features added to your understanding of the old interface!

Empty object schema ****

scenario

Take a look at a DTO transformation scenario, object:

@Data
static class PersonDTO{
  private String dtoName;
  private String dtoAge;
}
@Data
static class Person{
  private String name;
  private String age;
}
Copy the code

The requirement is to convert the Person object to PersonDTO and then return it.

In practice, of course, returning null if Person is empty will return null, but PersonDTO cannot return NULL (especially the dTOS returned by Rest interfaces).

In this case, let’s just focus on the conversion operation, and look at the following code:

@Test
public void shouldConvertDTO(){
  PersonDTO personDTO = new PersonDTO();
  Person person = new Person();
  if(! Objects.isNull(person)){ personDTO.setDtoAge(person.getAge()); personDTO.setDtoName(person.getName()); }else{
    personDTO.setDtoAge("");
    personDTO.setDtoName(""); }}Copy the code

Optimize the modification

Such data conversion, very poor readability, each field judgment, if empty is set to empty string (” “)

In another way of thinking, we get the data of the Person class and then perform the assignment operation (setXXX). In fact, it does not matter who the specific implementation of Person is.

Create a Person subclass:

static class NullPerson extends Person{
  @Override
  public String getAge() {
    return "";
  }
  @Override
  public String getName() {
    return ""; }}Copy the code

It exists as a special case of Person, and returns some default behavior of GET * when Person is empty.

So the code can be changed to:

@Test
 public void shouldConvertDTO(){
   PersonDTO personDTO = new PersonDTO();
   Person person = getPerson();
   personDTO.setDtoAge(person.getAge());
   personDTO.setDtoName(person.getName());
 }
 private Person getPerson(){
   return new NullPerson();   // If Person is null, an empty object is returned
 }
Copy the code

The getPerson() method can be used to retrieve possible objects of Person based on the business logic (NUllPerson is the NUllPerson exception that returns Person if the Person does not exist in the current example). If this is changed, the code will become much more readable.

Optimizations can be made using Optional

Empty object model, its malpractice lies in the need to create a special object, but if an exception is more, if we need to create multiple exception object, although we also use the object-oriented polymorphism characteristics, however, the complexity of the business if really let us create multiple exception object, we have to think twice about this model, It can introduce code complexity.

The above code can also be optimized using Optional.

@Test
  public void shouldConvertDTO(){
    PersonDTO personDTO = new PersonDTO();
    Optional.ofNullable(getPerson()).ifPresent(person -> {
      personDTO.setDtoAge(person.getAge());
      personDTO.setDtoName(person.getName());
    });
  }
  private Person getPerson(){
    return null;
  }
Copy the code

Optional’s use of null values, which I think is more appropriate, only applies to the “exist or not” scenario.

If you are judging only the presence of control, I recommend Optional.

Correct use of Optioanl

Optional is so powerful that it expresses a primitive feature of the computer (0 or 1), so how can it be used correctly?

Do not use Optional as a parameter

If you write a public method that specifies input parameters, some of which can be passed as null, can you use Optional?

Advice: don’t use it like this!

Here’s an example:

public interface UserService{
  List<User> listUser(Optional<String> username);
}
Copy the code

The listUser method in this example probably tells us that we need to query all data sets based on username, and if username is empty, return all user sets as well.

There are some ambiguities when we look at this method:

“If username is absent, return empty set? Or return the entire set of user data?”

Optioanl is a branching judgment, so do we focus on Optional or option.get ()?

My advice to you is, if you don’t want this ambiguity, don’t use it!

If you really want to express two meanings, split it into two interfaces:

public interface UserService{
  List<User> listUser(String username);
  List<User> listUser();
}
Copy the code

I think this is more semantic and more consistent with the “single responsibility” principle of software design.

If you feel that null is necessary for your input, use JSR 303 or JSR 305 for verification!

Please remember that! Optional cannot be used as an input parameter!


Optional is returned as ****

When an entity returns

Can Optioanl be returned?

In fact, it is very satisfied with the existence of semantics.

For example, you want to get user information based on id, and the user may or may not exist.

You can use it like this:

public interface UserService{
  Optional<User> get(Integer id);
}
Copy the code

When calling this method, the caller is aware that the data returned by the get method may not exist, so it can make some more reasonable judgments and better prevent null pointer errors!

Of course, if the business really needs to query User based on id, do not use this method. Please specify the exception you want to throw.

Do Optional returns only if it is reasonable to consider it returning NULL

The return of the collection entity

Not all return values can be used this way! If you return a collection:

public interface UserService{
  Optional<List<User>> listUser();
}
Copy the code

The result will confuse the caller. Should I use isEmpty after judging Optional?

This brings return value ambiguity! I don’t think it’s necessary.

We want to stipulate that for a collection like List, if the collection is really null, return the empty collection (Lists. NewArrayList);


Use the Optional variable ****

Optional<User> userOpt = ...
Copy the code

If there is such a variable userOpt, remember:

  • You should never use get directly. If you do, you lose the Optional meaning (userop.get ()).
  • Don’t use getOrThrow directly if you have a need to throw an exception if you can’t get it. Consider whether the interface of the call is properly designed

Use in getter

For a Java bean, all properties may return NULL. Do I need to rewrite all getters to be Optional?

Don’t abuse Optional in this way.

Even if the getters in my Java beans are Optional, because there are so many Java beans, your code will be more than 50% Optinal, which will pollute the code. (I would like to say that the fields in your entity should be defined by the business, and you should consider the value of their existence carefully. Don’t abuse them because Optional exists.)

We should be more concerned with the business, not just null value judgment.

Don’t abuse Optional in getters.

summary

The use of Optional can be summarized as follows:

  • When using a null value that is not due to an error, you can use Optional!
  • Optional should not be used for collection operations!
  • Do not abuse Optional, such as in the getters of Java beans!

Source: lrwinx. Making. IO