最近看到《阿里巴巴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
?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;
????????}
????}
如果price
对应的所有的值为null
,那么算出来的和为null
。
如果采用ifnull
函数就可以求和就是0这样就可以避免空指针。
null
的时候笔者就是由于存储了null
值造成生产事故,差点被开除了!详细介绍可以阅读以前文章《Java采坑记》
如果项目里面就是有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相同也会抛异常,感兴趣的同学可以动手试试。
在这里插入图片描述
在这里插入图片描述
JDK1.8
提供的Optional
来避免NPE
。参考 《阿里巴巴泰山版Java开发手册》(公众号回复[开发手册]免费获取)
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。