How does Java handle null values more gracefully?

Time:2021-8-30

Source: sigma blog
https://lrwinx.github.io/
2019-12-17 09:39:00

In my several years of development experience, I often see that there are null value judgments everywhere in the project. These judgments will make people feel confused. Its emergence is likely to have nothing to do with the current business logic. But it will give you a headache.

Sometimes, the more terrible thing is that the system will throw null pointer exceptions because of these null values, resulting in problems in the business system.

In this article, I have summarized several methods of dealing with null values, hoping to be helpful to readers.

Null value in business

scene

There is a usersearchservice to provide user query functions:

public interface UserSearchService{
  List listUser();

  User get(Integer id);
}

Problem site

For object-oriented languages, the level of abstraction is particularly important. Especially the abstraction of interface, which accounts for a large proportion in design and development. We hope to make interface oriented programming as much as possible.

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

  • Listuser(): query the user list

  • Get (integer ID): query a single user

In all development, the TDD mode advocated by XP can well guide us to define the interface, so we take TDD as the “promoter” of code development.

For the above interfaces, potential problems are found when we use TDD to test case first:

  • If listuser () has no data, does it return an empty collection or null?

  • Get (integer ID) if there is no such object, will it throw an exception or return null?

In depth listuser research

Let’s discuss it first

listUser()

For this interface, I often see the following implementations:

public List listUser(){
    List userList = userListRepostity.selectByExample(new UserExample());
    If (collectionutils. Isempty (userlist)) {// spring util tool class
      return null;
    }
    return userList;
}

The return of this code is null. From my many years of development experience, it is best not to return null for the return value of a collection, because if NULL is returned, it will bring a lot of trouble to the caller. You will leave this call risk to the caller to control.

If the caller is a cautious person, he will make a conditional judgment on whether it is null. If he is not cautious, or he is a fanatic of interface oriented programming (of course, interface oriented programming is the right direction), he will call the interface according to his own understanding without judging whether it is null. If so, it is very dangerous, and it is likely to have null pointer exception!

According to Murphy’s Law:  “Problems that are likely to occur will certainly appear in the future!”

Based on this, we optimize it:

public List listUser(){
    List userList = userListRepostity.selectByExample(new UserExample());
    if(CollectionUtils.isEmpty(userList)){
      return   Lists.newArrayList();// The ways provided by the guava class library
    }
    return userList;
}

For interfaces(List listUser()), it will certainly return list. Even if there is no data, it will still return list (there is no element in the collection);

Through the above modifications, we have successfully avoided the possible null pointer exception, which is safer!

In depth study of get method

For interfaces

User get(Integer id)

What you can see is that if I give an ID, it will definitely return user to me. But the fact is very likely not.

I’ve seen implementations:

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

I believe many people will write like this.

When you pass the code, you know that its return value is likely to be null! But we can’t distinguish the interface we pass!

This is a very dangerous thing. Especially for callers!

My suggestion is to supplement the document when the interface is explicit. For example, for the description of exceptions, use the annotation @ exception:

public interface UserSearchService{

  /**
   *Obtain user information according to user ID
   *  @ param   ID user ID
   *  @ return   User entity
   * @exception UserNotFoundException
   */
  User get(Integer id);

}

After we have explained the interface definition, the caller will see that if this interface is invoked, it is likely to throw an exception such as “UserNotFoundException (not able to find the user)”.

In this way, you can see the interface definition when the caller calls the interface, but this method is “weak prompt”!

If the caller ignores the comments, there may be a risk to the business system, which may lead to 100 million!

In addition to the above “weak prompt” method, another way is that the return value may be empty**** What should we do?

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

Introduce the option of jdk8 or use the option of guava. See the following definitions:

public interface UserSearchService{

  /**
   *Obtain user information according to user ID
   *  @ param   ID user ID
   *  @ return   User entity, which may be the default value
   */
  Optional getOptional(Integer id);
}

Optional has two meanings: existence or default.

Then, by reading the interface getoptional (), we can quickly understand the intention of the return value. This is actually what we want to see. It removes ambiguity.

Its implementation can be written as:

public Optional getOptional(Integer id){
  return Optional.ofNullable(userRepository.selectByPrimaryKey(id));
}

Deep into the reference

Through the above description of all interfaces, can you confirm that the input parameter ID must be passed? I think the answer should be: not sure. Unless otherwise stated in the documentation notes of the interface.

How to constrain the input parameters?

I recommend two ways:

  • Mandatory constraint

  • Document constraint (weak prompt)

1. For mandatory constraints, we can make strict constraint declarations through JSR 303:

public interface UserSearchService{
  /**
   *Obtain user information according to user ID
   *  @ param   ID user ID
   *  @ return   User entity
   * @exception UserNotFoundException
   */
  User get(@NotNull Integer id);

  /**
   *Obtain user information according to user ID
   *  @ param   ID user ID
   *  @ return   User entity,此实体有可能是缺省值
   */
  Optional getOptional(@NotNull Integer id);
}

Of course, it should be written in this wayAOPThe operation is verified, but letspringA good integration scheme has been provided, so I won’t repeat it here. WeChat official account: Java technology stack, back in the background: spring, can get my latest N Spring   Series of tutorials are dry goods.

2. Document constraints

In many cases, we will encounter legacy code. For legacy code, the possibility of overall transformation is very small.

We prefer to explain the interface by reading the implementation of the interface.

JSR 305 specification gives us a way to describe interface input parameters (the library com. Google. Code. Findbugs: jsr305 needs to be introduced):

The annotation @ nullable @ nonnull @ checkfornull can be used for interface description.

For example:

public interface UserSearchService{
  /**
   *Obtain user information according to user ID
   *  @ param   ID user ID
   *  @ return   User entity
   * @exception UserNotFoundException
   */
  @CheckForNull
  User get(@NonNull Integer id);

  /**
   *Obtain user information according to user ID
   *  @ param   ID user ID
   *  @ return   User entity,此实体有可能是缺省值
   */
  Optional getOptional(@NonNull Integer id);
}

Summary

Returns a value through an empty collection,Optional, JSR 303 and JSR 305 can make our code more readable and have a lower error rate!

  • Empty collection return value: if a collection returns a value like this, unless you really have a reason to convince yourself, you must return an empty collection instead of null

  • Optional: if your code is jdk8, introduce it! If not, use guava’sOptional, or upgrade the JDK version! It can greatly increase the readability of the interface!

  • JSR 303: if a new project is under development, try this! There must be a special feeling!

  • JSR 305: if the old project is in your hands, you can try to add this kind of document annotation, which is helpful for your later refactoring, or if new functions are added, your understanding of the old interface!

Empty object mode

scene


Let’s take a look at a dto conversion scenario, object:

@Data
static class PersonDTO{
  private String dtoName;
  private String dtoAge;
}

@Data
static class Person{
  private String name;
  private String age;
}

The requirement is to convert the person object into a person dto and then return it.

Of course, for the actual operation, if the return person is empty, it will return null, but the person dto cannot return null (especially the dto returned by the rest interface).

Here, we only focus on the conversion operation. See 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("");
  }
}

Optimization modification

For such data conversion, we know that the readability is very poor. The judgment of each field is set to an empty string (“”) if it is empty

In another way of thinking, we get the data of the person class and then perform the assignment operation (setXXX). In fact, it doesn’t matter who implements the person.

Then we can create a person subclass:

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

  @Override
  public String getName() {
    return "";
  }
}

It exists as a special case of person. If person is empty, some default behaviors of get * will be returned

Therefore, the code can be modified 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
 }

The getperson () method can be used to obtain the possible objects of person according to the business logic (for the current example, if person does not exist, return the special case nullperson of person). If it is modified to this way, the readability of the code will become very strong.

You can optimize with optional

The disadvantage of the empty object model is that it needs to create a special case object, but if there are many special cases, do we need to create multiple special case objects? Although we also use the object-oriented polymorphism, we still need to think about this model again if we really need to create multiple special case objects due to the complexity of the business, It can lead to code complexity.

For the above code, you can also use optional for optimization.

@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;
  }

OptionalI think the use of null value is more appropriate. It is only applicable to the “existence” scenario.

If you only judge the existence of control, I suggest usingOptional

Proper use of optioanl

OptionalSo powerful, it expresses the most primitive characteristics of the computer (0 or 1), how can it be used correctly!

Optional do not use as a parameter

If you write a public method that specifies some input parameters, some of which can be null, can you use it at this timeOptionalAnd?

My advice is: don’t use it like this!

for instance:

public interface UserService{
  List listUser(Optional username);
}

The method listuser in this example may tell us that we need to query all data sets according to username. If username is empty, we also need to return all user sets

When we see this method, we will feel some ambiguity:

“If username is absent, does it return an empty collection? Or return all user data sets? “

OptionalIs a branch of judgment, then what are we concerned about  OptionalstillOptionalWhat about. Get()?

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

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

public interface UserService{
  List listUser(String username);
  List listUser();
}

I think this semantics is stronger and can better meet the “single responsibility” in the software design principle.

If you think your input parameter is really necessary and may pass null, please use JSR 303 or JSR 305 for description and verification!

Please remember! Optional cannot be used as parameter of input parameter!

**
Optional as return value**

When an entity returns

thatOptionalCan it be used as a return value?

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

For example, you need to obtain user information according to the ID. this user may or may not exist.

You can use this:

public interface UserService{
  Optional get(Integer id);
}

When calling this method, the caller knows that the data returned by the get method may not exist. This can make more reasonable judgments and better prevent null pointer errors!

Of course, if the business party really needs to query the user according to the ID, do not use it like this. Please explain the exceptions you want to throw

Only if it is reasonable to consider that it returns nullOptionalReturn of

Return of collection entities

Not all return values can be used like this! If you are returning a collection:

public interface UserService{
  Optional> listUser();
}

Such a return result will make the caller overwhelmed. Do I judgeOptionalAfter that, you still use isempty to judge?

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

We need to agree that for the return value of a set such as list, if the set is really null, please return an empty set (lists. Newarraylist);

Using the optional variable

Optional userOpt = ...

If you have such a variable useropt, remember:

  • You must not use get directly. If you use it in this way, it will be lostOptionalMeaning of itself (such as userop. Get())

  • Don’t use getorthrow directly. If you have such a requirement: throw an exception if you can’t get it. It is necessary to consider whether the interface design of the call is reasonable

Use in Getters

For ajava bean, all properties may return null. Do you need to rewrite all gettersOptionalWhat about the type?

My advice to you is not to abuse option like this

Even if the getter in my java bean matchesOptionalYes, but because there are too many Java beans, it will lead to more than 50% of your codeOptionalJudgment, which pollutes the code( I would like to say that in fact, all fields in your entity should have business meanings. I will seriously think about the value of its existence and can’t abuse it because of the existence of option)

We should pay more attention to the business, not just the judgment of null value.

Please do not abuse optional. In getters

Summary

The use of optional can be summarized as follows:

  • Optional can be used when the value is empty and is not due to an error

  • Optional, do not use for collection operations!

  • Don’t abuse optional, such as in the getter of Java beans!

Recent hot article recommendations:

1.Java 15 officially released, 14 new features, refresh your understanding!!

2.Finally got the IntelliJ idea activation code through the open source project. It’s really fragrant!

3.I wrote a section of logic in Java 8. My colleagues can’t understand it directly. Try it..

4.Drop tomcat, the performance of underwow is very explosive!!

5.Java development manual (Songshan version) is the latest release. Download it quickly!

Feel good, don’t forget to like + forward!