33

万恶的 NPE 差点让我半个月工资没了

 3 years ago
source link: https://mp.weixin.qq.com/s/xoOalilXsmeOcvpjf24Z9g
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

最近看到《阿里巴巴Java开发手册》(公众号回复[ 开发手册 ]免费获取)第11条规范写到:

防止 NPE ,是程序员的基本修养

NPE(Null Pointer Exception) 一直是开发中最头疼的问题,也是最容易忽视的地方。记得刚开始工作的时候所在的项目组线上出现最多的 bug 不是逻辑业务 bug 而是 NPE ,所以后面项目组出了一个奇葩的规矩,线上如果谁出现一个NPE的问题就罚款 100元 ,用作团建费用。如果项目组每个人一个月都出现个两三个 NPE 的话。那么项目组是不是每个月都可以去团建下(自己掏钱海吃海喝,心不心疼)。不过自从这个规矩实施以来,线上的 NPE 就渐渐的少了,从最初的一个月团建一次到最后的半年团建一次。大家写代码都比较谨慎了,只要用到对象或者集合的时候二话不说上来先判空,所以产生的 NPE 就少了。

业务中返回结果的空值

在我们常见的业务开发中是不是经常会有这样的接口:

package com.workit.demo.nullexcption;

import com.workit.demo.proxy.User;

import java.util.List;

public interface IUserSearchService {
/**
* 查询用户列表
* @return
*/

List<User> listUser();

}

这个接口是不是存在两个潜在的问题?

  • listUser 这个方法 如果没有数据,那它是返回空集合还是null呢?
  • getUserById
    ID
    listUser
    
 public List<User> listUser() {
List<User> userList = userRepository.listUser();
if (null == userList || userList.size() == 0) {
return null;
}
return userList;
}

这种实现如果调用者是一个严谨的人或者像我这样被 NPE 罚款买过单的人,是会对返回结果进行 null 的判断。如果调用者并非谨慎的人或者刚刚入门的人,他就会按照自己的理解去调用接口,拿到结果就不管三七二十一上来对结果就是一顿循环操作,而不进行是否为 null 的条件判断,如果这样的话,是非常危险的,它很有可能出现空指针异常!这就是在代码中埋了一个定时炸弹,不知道什么时候就会爆炸。 UBjy2af.gif 由于存在这种不安全的隐患我们可以看下第二种实现:

  public List<User> listUser() {
List<User> userList = userRepository.listUser();
if (null == userList || userList.size() == 0) {
return new ArrayList<>();
}
return userList;
}

对于这种实现它一定会返回 List ,即使没有数据,也会返回一个空集合。通过以上的修改,我们成功的避免了有可能发生的空指针异常,这样的写法更安全!那针对于上面的两种实现,一个是需要调用者进行判空,一个是提供接口的人返回默认值。那我们到底应该用哪种方式呢?这种情况《阿里巴巴开发手册》也有明确规定: aIzMzuy.png!web 所以还是那句话使用 任何对象或者集合之前记得先判空

业务中请求参数空值

 /**
* 根据用户ID查询当前用户
* @param id
* @return
*/

User getUserById(Integer id);

这个接口的描述,你能确定入参 id 一定是必传的吗?我觉得答案应该是:不能确定。除非接口的文档注释上加以说明。那么我们应该怎样来约束入参呢?

  • 强制约束

  @Override
public User getUserById(Integer id) {
if (Objects.isNull(id)){
throw new IllegalArgumentException("id不能为空");
}
return null;
}

通过 jsr 303 进行严格的约束声明配合AOP的操作进行验证。

User getUserById(@NotNull  Integer id);

其他需要注意的NPE

switch中的空指针异常

看下面的列子妥妥的 NPE

 public static void main(String[] args) {
eat(null);
}
enum EatType{
Breakfast,Lunch,Dinner;
}
public static void eat(EatType eatType){
switch(eatType){
case Breakfast:
System.out.println("吃早饭");
break;
case Lunch:
System.out.println("吃中饭");
break;
case Dinner:
System.out.println("吃晚饭");
break;
default:
System.out.println("输入错误");
break;
}
}

数据库的sum函数

FzIzUj.png!web 如果 price 对应的所有的值为 null ,那么算出来的和为 nullYbUrUby.png!web 如果采用 ifnull 函数就可以求和就是0这样就可以避免空指针。 mMNv2ue.png!web

使用Map类集合时需要注意存储值为 null 的时候

笔者就是由于存储了 null 值造成生产事故,差点被开除了!详细介绍可以阅读以前文章《Java采坑记》 YnMV3ii.png!web

使用 java.util.stream.Collectors 类的 toMap()方法注意value为空时

bIFZfuZ.png!web 如果项目里面就是有null值怎么办呢?可以用下面几种方法来解决:

  • 过滤值为null

  • 换一种写法

  • 据说这个问题 java9 就修复了,所以也可以尝试升级jdk
  List<Pair<String, Double>> pairArrayList = new ArrayList<>(2);
pairArrayList.add(new Pair<>("version1", 4.22));
pairArrayList.add(new Pair<>("version2", null));
// 第一种过滤值为null的
Map<String, Double> map = pairArrayList.stream().filter(p-> Objects.nonNull(p.getValue())).collect(
Collectors.toMap(Pair::getKey, Pair::getValue, (v1, v2) -> v2));
System.out.println(map.toString());
// 换一种实现方式
LinkedHashMap<Object, Object> collect = pairArrayList.stream().collect(LinkedHashMap::new, (m, v) -> m.put(v.getKey(), v.getValue()), LinkedHashMap::putAll);
System.out.println(collect.toString());

输出结果

{version1=4.22}
{version1=4.22, version2=null}

这个方法还有一个坑 如果key相同也会抛异常 ,感兴趣的同学可以动手试试。

使用 Collection 接口任何实现类的 addAll()方法时,都要对输入的集合参数进行NPE 判断。

RbUZree.png!web在这里插入图片描述

三目运算符可能产生NPE

BfIF7bB.png!web在这里插入图片描述

那么如何有效的避免NPE呢

  • 使用对象或者集合之前记得先判空。

  • 使用JDK一些API的方法记得要点进源码去大概看看,不要随便拿来就用。

  • 单元测试要对空值进行测试,保证程序的健壮性。

  • JDK1.8
    Optional
    NPE
    
  • 提供接口时候需要对非空参数进行说明,并且对非空参数进行校验,不要太相信调用者。

  • 调用接口的时候一定要对接口返回值进行判空,不要太相信接口提供者。(这个肯定会有值的)。

  • 小心使得万年船

结束

  • 由于自己才疏学浅,难免会有纰漏,假如你发现了错误的地方,还望留言给我指出来,我会对其加以修正。

  • 如果你觉得文章还不错,你的转发、分享、赞赏、点赞、留言就是对我最大的鼓励。

  • 感谢您的阅读,十分欢迎并感谢您的关注。

  • 2yInMrZ.png!web

历史推荐

终于有人把 java代理 讲清楚了,万字详解!

java采坑之路

超长JVM总结,面试必备

参考

《阿里巴巴泰山版Java开发手册》(公众号回复[ 开发手册 ]免费获取)


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK