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:
- Call instance method of null object
- Access or modify properties of null objects
- Gets the length of the array whose value is null
- When accessing or modifying a column of a two-dimensional array with a value of null
- 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