Alibaba Java Development Manual Codes Effective Details-An Analysis of the Grinding Null Pointer Problem

Keywords: Java Lombok Attribute Apache

1 Leader

Say nothing but read the manual guidelines first

The manual has helped us summarize common problem scenarios, but we still need to dig into the null pointer problem in order to get it right.

2 What is the null pointer in the world?

2.1 Official Resolution


A null is passed in when the application needs an object, including the following scenarios:

  1. Call instance method of null object
  2. Access or modify properties of null objects
  3. Gets the length of the array whose value is null
  4. When accessing or modifying a column of a two-dimensional array with a value of null
  5. When null is thrown as a Throwable object.

For example, as mentioned in the manual

Can be classified as case4

As in the Manual:

It can be categorized as case1 because null is possible at each level.

Therefore, when encountering these scenarios in development, it is important to note code handling to prevent null pointers.

2.2 Inheritance Chart

It is clear that NPE inherits from RuntimeException and is a subclass of Exception.

3. Null Pointer Case Display

3.1 Basic bug production scenarios

	@Test
    public void test() {
        Assertions.assertThrows(NullPointerException.class, () -> {
            List<UserDTO> users = new ArrayList<>();
            users.add(new UserDTO(1L, 3));
            users.add(new UserDTO(2L, null));
            users.add(new UserDTO(3L, 3));
            send(users);
        });

    }

    // First
    private void send(List<UserDTO> users) {
        for (UserDTO userDto : users) {
            doSend(userDto);
        }
    }

    private static final Integer SOME_TYPE = 2;

    private void doSend(UserDTO userDTO) {
        String target = "default";
        // Second
        if (!userDTO.getType().equals(SOME_TYPE)) {
            target = getTarget(userDTO.getType());
        }
        System.out.println(String.format("userNo:%s, Send to%s Success", userDTO, target));
    }

    private String getTarget(Integer type) {
        return type + "Base No.";
    }
  • In the first place, throw NPE if the set is null
  • In the second place, throw NPE if the type property is null

You may think this example is too simple. If you see null as an input parameter, you will certainly consider the null pointer. But when you write the external interface yourself, you will not know if the parameter passed from outside is null.

3.2 Fixed self-sealing return of objects

To avoid null pointers or checking for null parameter throws, return an object created by a null parameter constructor directly:

/**
 * Query orders based on order number
 *
 * @param orderNo Order Number
 * @return Order
 */
public Order getByOrderNo(String orderNo) {

    if (StringUtils.isEmpty(orderNo)) {
        return new Order();
    }
    // Query order
    return doGetByOrderNo(orderNo);
}

Query interface for single data, throws an exception or returns null when parameters are checked for inconsistencies.
This is rarely the case because it is the custom of external callers to use fields directly without seeing a null result.

External calls determine that the return value is not null, and the external calls the instance function boldly, resulting in NPE.

3.3 @NonNull attribute deserialization

The RPC interface with an order update has an OrderUpdateParam parameter, preceded by two attributes: id and name.
When a requirement is met, an extra attribute is added and the field cannot be null.

Use lombok's @NonNull annotation to avoid null pointers:

import lombok.Data;
import lombok.NonNull;

import java.io.Serializable;

@Data
public class OrderUpdateParam implements Serializable {
    private static final long serialVersionUID = 3240762365557530541L;

    private Long id;

    private String name;

     // Other Attributes
  
    // New Attributes
    @NonNull
    private String extra;
}

The service that did not use the latest jar package reported an error in its RPC call to the interface when it came online.

Let's analyze why IDEA automatically decompiles DEMO-compiled class files found in the target-classes directory of IDEA:

public class OrderUpdateParam implements Serializable {
    private static final long serialVersionUID = 3240762365557530541L;
    private Long id;
    private String name;
    @NonNull
    private String extra;

    public OrderUpdateParam(@NonNull final String extra) {
        if (extra == null) {
            throw new NullPointerException("extra is marked non-null but is null");
        } else {
            this.extra = extra;
        }
    }

    @NonNull
    public String getExtra() {
        return this.extra;
    }
    public void setExtra(@NonNull final String extra) {
        if (extra == null) {
            throw new NullPointerException("extra is marked non-null but is null");
        } else {
            this.extra = extra;
        }
    }
  // Other Codes

}

You can also use the decompile tool: JD-GUI to decompile the compiled class file and view the source code.

Since the caller calls a jar package without extra attribute and the serialization numbers are consistent, NPE is thrown when deserializing.

Caused by: java.lang.NullPointerException: extra

‚Äč        at com.xxx.OrderUpdateParam.<init>(OrderUpdateParam.java:21)

When adding the @NonNull comment for lombok to the RPC parameter, consider whether the caller updates the jar package in time to avoid null pointers.

3.4 Auto-unboxing

Case 1

@Data
/**
 * Who we serve ourselves
 */
public class GoodCreateDTO {
    private String title;

    private Long price;

    private Long count;
}

@Data
/**
 * The parameter object for which we call the service
 */
public class GoodCreateParam implements Serializable {

    private static final long serialVersionUID = -560222124628416274L;
    private String title;

    private long price;

    private long count;
}

The count field of GoodCreateDTO is not a required parameter in our system and may be null in this system.
If we are not used to pulling the source code, convert it directly through the previous conversion tool class.We subconsciously assume that the object type of the external interface is also the wrapper type, and NPE can easily occur due to conversion.

  • Conversion Tool Class
public class GoodCreateConverter {

    public static GoodCreateParam convertToParam(GoodCreateDTO goodCreateDTO) {
        if (goodCreateDTO == null) {
            return null;
        }
        GoodCreateParam goodCreateParam = new GoodCreateParam();
        goodCreateParam.setTitle(goodCreateDTO.getTitle());
        goodCreateParam.setPrice(goodCreateDTO.getPrice());
        goodCreateParam.setCount(goodCreateDTO.getCount());
        return goodCreateParam;
    }
}

When the converter executes to goodCreateParam.setCount(goodCreateDTO.getCount()); the automatic unboxing will be reported to NPE

When the count property of GoodCreateDTO is null, automatic unboxing will report NPE

Case 2

Call the following two-party service interface:

public Boolean someRemoteCall();

Then think that the other party will definitely return TRUE or FALSE, and then use it directly as a criterion or basic type, if it returns null, it will report NPE.

if (someRemoteCall()) {
           // Business Code
 }

3.5 Batch call merge result space-time pointer

Some batch queries can be called in small batches because the secondary interface of the query tends to time out when the data is large.

The following encapsulates a batch of data that splits List data into each size, calls the function RPC interface, and merges the results.

Imagine what happens if a batch request has no data and does not return an empty collection but null?
Unfortunately, another NPE is coming to you...

Decide how to handle the NPE s that may be generated here based on the specific business scenario

If the return value is null in a scenario and is definitely not allowed, the result can be checked in the function function, and if it is null, an exception can be thrown.

If allowed, null s can be filtered after map calls:

// Omit preceding code
.map(function)
.filter(Objects::nonNull)
// Omit subsequent code

4 Prevention Guidelines

4.1 Interface Developer

4.1.1 Returns an empty set

If the parameter does not meet the requirements and returns an empty set directly, the underlying function also uses a consistent approach:

public List<Order> getByOrderName(String name) {
    if (StringUtils.isNotEmpty(name)) {
        return doGetByOrderName(name);
    }
    return Collections.emptyList();
}

4.1.2 Use Optional

Optional is a feature introduced in Java 8, and returning an Optional explicitly tells the user that the result may be empty:

public Optional<Order> getByOrderId(Long orderId) {
    return Optional.ofNullable(doGetByOrderId(orderId));
}

If you are interested, you can enter Optional's source code, learn more with the codota tools described earlier, or learn with the relevant chapters of the Java 8 Actual War.

4.1.3 Use empty object design mode

In order to solve case1 caused by NPE, this design pattern often needs to be empty before continuing the method:

public void doSomeOperation(Operation operation) {
    int a = 5;
    int b = 6;
    if (operation != null) {
        operation.execute(a, b);
    }
}

The Zen of Design Patterns (2nd Edition) on page 554 tells about the Empty Object Patterns in an expanded book.

You can construct a NullXXX class that extends from an interface so that when the interface needs to be null, it simply returns the object:

public class NullOperation implements Operation {

    @Override
    public void execute(int a, int b) {
        // do nothing
    }
}

The above null operation is no longer necessary because we all return NullOperation where nulls are needed, and the corresponding object methods are available:

public void doSomeOperation(Operation operation) {
    int a = 5;
    int b = 6;
    operation.execute(a, b);
}

4.2 Interface Caller

4.2.1 null check

As clean code says

You can check parameters and throw exceptions to unsatisfactory conditions.

It is the simplest and most common practice to check for conditions that cannot be null and do not meet business requirements directly before use.

Defensive parameter detection can greatly reduce the probability of error and improve the robustness of the program:

@Override
public void updateOrder(OrderUpdateParam orderUpdateParam) {
    checkUpdateParam(orderUpdateParam);
    doUpdate(orderUpdateParam);
}

private void checkUpdateParam(OrderUpdateParam orderUpdateParam) {
    if (orderUpdateParam == null) {
        throw new IllegalArgumentException("Parameter cannot be empty");
    }
    Long id = orderUpdateParam.getId();
    String name = orderUpdateParam.getName();
    if (id == null) {
        throw new IllegalArgumentException("id Cannot be empty");
    }
    if (name == null) {
        throw new IllegalArgumentException("name Cannot be empty");
    }
}
  • JDK Thread Pool Method

  • AbstractApplicationContext#assertBeanFactoryActive in Spring

4.2.2 Using Objects

You can use the Objects class introduced in Java 7 to simplify code that determines null and throws a null pointer.

Use it as follows:

private void checkUpdateParam2(OrderUpdateParam orderUpdateParam) {
    Objects.requireNonNull(orderUpdateParam);
    Objects.requireNonNull(orderUpdateParam.getId());
    Objects.requireNonNull(orderUpdateParam.getName());
}
  • The principle is simple, let's look at the source code

4.2.3 Using the commons Toolkit

4.2.3.1 String Tool Class: org.apache.commons.lang3.StringUtils

public void doSomething(String param) {
    if (StringUtils.isNotEmpty(param)) {
        // Using param parameters
    }
}

4.2.3.2 Check tool class: org.apache.commons.lang3.Validate

public static void doSomething(Object param) {
    Validate.notNull(param,"param must not null");
}
public static void doSomething2(List<String> parms) {
    Validate.notEmpty(parms);
}

The checking tool class supports many types of checks, custom prompt text, and so on.

public static <T extends Collection<?>> T notEmpty(final T collection, final String message, final Object... values) {
    if (collection == null) {
        throw new NullPointerException(String.format(message, values));
    }
    if (collection.isEmpty()) {
        throw new IllegalArgumentException(String.format(message, values));
    }
    return collection;
}

This throws NPE if the collection object is null and IllegalArgumentException if the collection is empty.

4.2.4 Collection tool class: org.apache.commons.collections4.CollectionUtils

public void doSomething(List<String> params) {
    if (CollectionUtils.isNotEmpty(params)) {
        // Using params
    }
}

4.2.5 Use guava package

You can use the guava package's com.google.common.base.Preconditions precondition detection class.

Also look at the source code, which gives an example.The original code is as follows:

public static double sqrt(double value) {
    if (value < 0) {
        throw new IllegalArgumentException("input is negative: " + value);
    }
    // calculate square root
}

With Preconditions, the code can be simplified to:

public static double sqrt(double value) {
   checkArgument(value >= 0, "input is negative: %s", value);
   // calculate square root
 }

Spring example:

  • org.springframework.context.annotation.AnnotationConfigApplicationContext#register

  • org.springframework.util.Assert#notEmpty(java.lang.Object[], java.lang.String)

Although the specific tool classes used are different, the core ideas are the same.

4.2.6 Automation API

4.2.6.1 lombok # @Nonnull

 public void doSomething5(@NonNull String param) {
      // Using param
      proccess(param);
 }

Compiled code:

 public void doSomething5(@NonNull String param) {
      if (param == null) {
          throw new NullPointerException("param is marked non-null but is null");
      } else {
          this.proccess(param);
      }
  }

4.2.6.2 IntelliJ IDEA# @NotNull && @Nullable

The maven dependencies are as follows:

<!-- https://mvnrepository.com/artifact/org.jetbrains/annotations -->
<dependency>
    <groupId>org.jetbrains</groupId>
    <artifactId>annotations</artifactId>
    <version>17.0.0</version>
</dependency>

The use of @NotNull on parameters is very similar to the example above.

public static void doSomething(@NotNull String param) {
    // Using param
    proccess(param);
}

5. Summary

This section mainly describes the meaning of the null pointer, the common shooting position of the null pointer, and how to avoid the null pointer abnormality.The next section will show you what happens when a switch encounters a null pointer.

Reference resources

  • Alibaba Java Development Manual 1.5.0: Huashan Edition
  • <Java Language Specification: Java SE 8 Edition>
  • Coding Specification: Alibaba Java Development Manual Detailed
358 original articles published, 214 praised, 210,000 visits+
His message board follow

Posted by leatherback on Wed, 05 Feb 2020 16:45:04 -0800