总结Java 空指针异常隐藏最深的8 个场景

1. 自动拆箱出现 null

包装器类型自动拆箱为基础类型时极容易出现NPE。如下图示例,方法void initTask(int taskId),调用时taskId如果为 null,则会出现NPE。正确做法是 可能为 null 的属性,一律声明为包装器,此外从外部获取的变量一定要检查 null,进行防御式编程。

public void initTask(int taskId){

}

@Test
public void test(){
  Integer taskId = extractFromRequest();

  initTask(taskId);
}

2. 遍历集合 出现 null

集合List支持 foreach 遍历,但是如果List变量为null,则一定会发生空指针异常 NPE。如下代码所示,当ids为空时,会发生NPE。

正确做法,在遍历List之前,一定要进行空值检查。

List<Integer> ids = extractFromRequest();

for(Integer id: ids){

}

3. 集合数组中出现 null

此外集合 List 中对象可能为空,在遍历集合时,要检查集合中的对象是否为 Null,否则可能发生 NPE。

List<Integer> ids = extractFromRequest();

for(Integer id: ids){

   System.out.println(id.toString());
}

4. Optional.of() 出现 null

Optional 是 Java 8 提供的一个工具类,它可以以更加优雅的方式来处理空值。在使用 Optional 类的时候,有两种初始化方式,分别是 Optional.of(object)Optional.ofNullable(object)。需要注意的是,在使用 Optional.of(object) 的时候,如果 object 为 null,就会发生空指针异常NPE。

正确做法:使用 Optional.ofNullable();

Optional.of(object); 

5. Stream和 Lamada中出现 null

若在lambda表达式中出现了Null,就可能会发生空指针异常 NPE。具体示例如下图所示,当使用Stream.map方式进行映射时,可能会导致返回值为Null。

正确做法:filter(Objects::nonNull) 过滤为 Null 对象

List<Long> userIds = Lists.newArrayList(1L, 2L, 3L);
List<String> orderIds = userIds.stream()
                    .map((userId) -> {
                       OrderService.UserOrder order = getUserOrderFromDb(userId);

                       return order;
                    }).map(OrderService.UserOrder::getOrderId)
                      .collect(Collectors.toList());

6. json 解析出现 null

使用 fastjson 可能遇到解析的对象为 null 的情况。很多业务系统使用 json 存储扩展字段,一般情况下mysql 字段默认值为””或 null,这种情况使用 FastJson 解析时,就会解析出 null对象,不判空就会出现空指针异常。

如下代码所示,当传入的json 字符串为空值””时,解析出的 json 对象为 Null。

UserOrder his = JSON.parseObject("{}", UserOrder.class);


Map<String, String> jsonMap = JSONObject.parseObject(ext_json,
      new TypeReference<HashMap<String,String>>(){});

7. 打印日志使用 + 号出现 null

日志打印时,很多人习惯使用 + 加号拼接日志,这种情况可能导致 空指针异常。

正确做法:使用{}占位符方式,打印变量。 日志框架会进行判空,当变量出现 Null 时,不会出现空指针。

很多人在 review 代码时,不重视日志代码,容易忽略日志代码中的问题。曾经有个同事搞出的线上问题就是因为日志打印出现了 NPE。

一定要敬畏每一行代码,包括日志代码。

8. 返回值异常处理

如下所示,业务系统在调用 Rpc 后,会对结果判空和检查异常码。如果调用失败,则会上报异常码。然而这两者不能完全放在一起,因为当 result 为 null 时,上报异常码会导致空指针异常的发生。

RpcResult result = invokeRpcMethod();
if(result==null || result.getCode!=SUCCESS){
     log.error("调用失败 result:{}", result);

     reportCode(result.getCode());
}
请登录后发表评论

    没有回复内容