万恶的NPE差点让我半个月工资没了
引言
最近看到《阿里巴巴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没有找到用户,是抛异常还是返回null呢?
首先我们先看下listUser这个方法的实现:
public List<User> listUser() {
List<User> userList = userRepository.listUser();
if (null == userList || userList.size() == 0) {
return null;
}
return userList;
}
这种实现如果调用者是一个严谨的人或者像我这样被NPE罚款买过单的人,是会对返回结果进行null的判断。如果调用者并非谨慎的人或者刚刚入门的人,他就会按照自己的理解去调用接口,拿到结果就不管三七二十一上来对结果就是一顿循环操作,而不进行是否为null的条件判断,如果这样的话,是非常危险的,它很有可能出现空指针异常!这就是在代码中埋了一个定时炸弹,不知道什么时候就会爆炸。

由于存在这种不安全的隐患我们可以看下第二种实现:
public List<User> listUser() {
List<User> userList = userRepository.listUser();
if (null == userList || userList.size() == 0) {
return new ArrayList<>();
}
return userList;
}
对于这种实现它一定会返回List,即使没有数据,也会返回一个空集合。通过以上的修改,我们成功的避免了有可能发生的空指针异常,这样的写法更安全!
那针对于上面的两种实现,一个是需要调用者进行判空,一个是提供接口的人返回默认值。那我们到底应该用哪种方式呢?这种情况《阿里巴巴开发手册》也有明确规定:
所以还是那句话使用任何对象或者集合之前记得先判空。
业务中请求参数空值
/**
* 根据用户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函数

如果price对应的所有的值为null,那么算出来的和为null。

如果采用ifnull函数就可以求和就是0这样就可以避免空指针。

使用Map类集合时需要注意存储值为null的时候
笔者就是由于存储了null值造成生产事故,差点被开除了!详细介绍可以阅读以前文章《Java采坑记》

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

如果项目里面就是有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 判断。

三目运算符可能产生NPE

那么如何有效的避免NPE呢
- 使用对象或者集合之前记得先判空。
- 使用JDK一些API的方法记得要点进源码去大概看看,不要随便拿来就用。
- 单元测试要对空值进行测试,保证程序的健壮性。
- 合理的使用
JDK1.8提供的Optional来避免NPE。 - 提供接口时候需要对非空参数进行说明,并且对非空参数进行校验,不要太相信调用者。
- 调用接口的时候一定要对接口返回值进行判空,不要太相信接口提供者。(这个肯定会有值的)。
- 小心使得万年船
结束
- 由于自己才疏学浅,难免会有纰漏,假如你发现了错误的地方,还望留言给我指出来,我会对其加以修正。
- 如果你觉得文章还不错,你的转发、分享、赞赏、点赞、留言就是对我最大的鼓励。
- 感谢您的阅读,十分欢迎并感谢您的关注。

参考
《阿里巴巴泰山版Java开发手册》(公众号回复[开发手册]免费获取)
万恶的NPE差点让我半个月工资没了的更多相关文章
- 用了这个jupyter插件,我已经半个月没打开过excel了
1 简介 jupyter lab是我迄今为止体验过开展数据分析等任务最舒适的平台,但这不代表它是完美的,因为在很多方面它仍然存在欠缺,譬如在对csv文件的交互式编辑方面. 图1 而本文将要介绍的jup ...
- MySQL半同步主从.md
MySQL Semisynchronous Replication 复制架构衍生史 1.普通的replication,异步同步. 搭建简单,使用非常广泛,从mysql诞生之初,就产生了这种架构,性能非 ...
- VS2010 编译安装boost库
实践是最好的办法..学习C++,想试试线程,然后打算用boost库,结果boost库编译差点吓到我..没看到比较完整的安装教程..一直耽搁.今天动手.完成了.方法记录如下:1.下载boost从boos ...
- CF Good Bye 2018
前言:这次比赛爆炸,比赛时各种想多,导致写到\(D\)题时思路已经乱了,肝了\(1\)个多小时都没肝出来,\(B\)题中途因为没开\(long\ long\)又被\(HACK\)了..\(C\)题因为 ...
- NOIWC2019 冬眠记
冬眠 由于THUWC考太差了没啥心情做事…… Day -1 报到日前一天晚上去看了看宿舍表,发现周围全是集训队,隔壁就是栋爷.高队和lca,再隔壁是zzq和wxh……吓傻了(本校buff这么好吗) D ...
- 2018年 第43届ACM-ICPC亚洲区域赛(青岛)现场赛 赛后总结
下了动车后,又颠颠簸簸的在公交车上过了接近一个小时,本来就晕车,于是,到的时候脑子晕死了,而且想吐.可能是没吃早饭的缘故,午饭好好次QWQ. 开幕式 还是第一次在这种环境下参赛,记得以前是看老师发的学 ...
- 冲刺$\mathfrak{CSP-S}$集训模拟赛总结
开坑.手懒并不想继续一场考试一篇文. 既没必要也没时间侧边栏的最新随笔题解反思相间也丑 而且最近越来越懒了竟然都不写题解了……开坑也是为了督促自己写题解. 并不想长篇大论.简要题解也得写啊QAQ. 目 ...
- javascript之闭包理解以及应用场景
半个月没写博文了,最近一直在弄小程序,感觉也没啥好写的. 之前读了js权威指南,也写了篇博文,但是实话实说当初看闭包确实还是一头雾水.现在时隔一个多月(当然这一段时间还是一直有在看闭包的相关知识)理解 ...
- 领域驱动设计实战—基于DDDLite的权限管理OpenAuth.net
在园子里面,搜索一下“权限管理”至少能得到上千条的有效记录.记得刚开始工作的时候,写个通用的权限系统一直是自己的一个梦想.中间因为工作忙(其实就是懒!)等原因,被无限期搁置了.最近想想,自己写东西时, ...
随机推荐
- Memtest在CentOS下的使用方法。
#memtest,指定测试大小范围248G,指定测试1次 nohup memtester 248G 1 > mem218.log&
- 卡耐基梅隆大学(CMU)元学习和元强化学习课程 | Elements of Meta-Learning
Goals for the lecture: Introduction & overview of the key methods and developments. [Good starti ...
- 【GDKOI2014】JZOJ2020年8月13日提高组T2 石油储备计划
[GDKOI2014]JZOJ2020年8月13日提高组T2 石油储备计划 题目 Description Input Output 对于每组数据,输出一个整数,表示达到"平衡"状态 ...
- 20200203_windows2012下安装mysql 5.7.29
一. 检查系统版本: 二. 下载mysql, 下载地址: https://dev.mysql.com/downloads/mysql/5.7.html#downloads 三. 解压下载后的压 ...
- Promise自定义,看我如何征服你
自定义代码 这里是我自定义的Promise,如果想看原版,可以跳过最后有符合PromiseA+规范的源码 class 承诺 { constructor(处理器函数) { //1. 处理器函数是在_ne ...
- 第14章 web前端开发小白学爬虫结束语
老猿学爬虫应该是2019年7月初开始的,到现在2个多月了,有段时间了,这部分一直是老猿期待能给大家带来收获的,因为老猿爬虫实战应用的场景与网上老猿已知的场景基本都不一样,是从复用网站登录会话信息来开发 ...
- 第15.6节 PyQt5安装与配置
一. 引言 关于PyQt5的安装网上有很多的文章,老猿也是学习了好多,最后结合其他模块安装的知识发现其实安装很简单,就是直接使用pip或pip3安装就可以了,这样既无需预先下载好软件,也无需担心版本的 ...
- PyQt学习随笔:ListView控件增加列表项
ListView控件如果需要增加列表项,就是在对应数据存储中插入项,这又分两种情况,一种是已知列表数据存储,一种是未知数据存储.如果是未知数据存储,可以通过: ListView控件名.model() ...
- Python & PyQt学习随笔:PyQt主程序的基本框架
在完成UI设计将UI通过PyUic转成Py文件后,由于这个生成的文件每次通过PyUic生成时都会被覆盖,因此应用的主程序必须另外单独编写py文件.需要将UI生成的文件import到主程序的py文件中. ...
- PyQt(Python+Qt)学习随笔:Qt Designer中怎么给toolBar添加按钮
在Designer中创建了一个MainWindow窗体,当想在其中的toolBar中添加toolButton时发现怎么也放不上去,最终才发现toolBar中的按钮只能通过直接拖拽Action编辑器中的 ...