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

简介: 万恶的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
  • 提供接口时候需要对非空参数进行说明,并且对非空参数进行校验,不要太相信调用者。
  • 调用接口的时候一定要对接口返回值进行判空,不要太相信接口提供者。(这个肯定会有值的)。
  • 小心使得万年船

结束

  • 由于自己才疏学浅,难免会有纰漏,假如你发现了错误的地方,还望留言给我指出来,我会对其加以修正。
  • 如果你觉得文章还不错,你的转发、分享、赞赏、点赞、留言就是对我最大的鼓励。
  • 感谢您的阅读,十分欢迎并感谢您的关注。
目录
相关文章
|
存储 运维 jenkins
放弃"Jenkins"的种种理由,期待更好赋能研发的"持续交付平台"
Jenkins 很酷,但是不完美,有历史局限性造成的问题。本文仅从“如何更好给研发团队赋能的角度”,剖析Jenkins, 探讨理想的持续交付平台, 不带货无广告~
245 3
|
SQL 关系型数据库 MySQL
MySQL数据库练习题(单表查询,多表关联查询)(一)
MySQL数据库练习题(单表查询,多表关联查询)
721 1
|
消息中间件 Java RocketMQ
SpringBoot整合RocketMQ发送批量消息
SpringBoot整合RocketMQ发送批量消息
|
6月前
|
固态存储 关系型数据库 数据库
从Explain到执行:手把手优化PostgreSQL慢查询的5个关键步骤
本文深入探讨PostgreSQL查询优化的系统性方法,结合15年数据库优化经验,通过真实生产案例剖析慢查询问题。内容涵盖五大关键步骤:解读EXPLAIN计划、识别性能瓶颈、索引优化策略、查询重写与结构调整以及系统级优化配置。文章详细分析了慢查询对资源、硬件成本及业务的影响,并提供从诊断到根治的全流程解决方案。同时,介绍了索引类型选择、分区表设计、物化视图应用等高级技巧,帮助读者构建持续优化机制,显著提升数据库性能。最终总结出优化大师的思维框架,强调数据驱动决策与预防性优化文化,助力优雅设计取代复杂补救,实现数据库性能质的飞跃。
908 0
|
Kubernetes Cloud Native Serverless
批处理系统:Batch批量计算与云原生Serverless Argo Workflows
本文对比了Batch批量计算与Serverless Argo Workflows在容器化批处理任务中的应用,分析了两者在任务定义、依赖关系、规模并发、高级编排、可移植性等方面的异同,帮助技术决策者根据自身需求选择合适的平台。
|
移动开发 网络协议 前端开发
每日一博 - Server-Sent Events推送技术
每日一博 - Server-Sent Events推送技术
596 0
|
数据采集 存储 自然语言处理
快速构建企业智能门户,销售额倍增,人才触手可及 - 爬虫 + RAG + LLM
本文介绍了一款基于大模型的智能企业门户接待系统,旨在通过先进的AI技术,实现企业网站信息的自动化处理与响应,提高客户支持、产品推荐和人才招聘的效率。系统利用爬虫技术自动提取公司官网信息,结合语音识别、大模型生成等技术,支持语音和文本输入,通过RAG(检索增强生成)方式生成精准回答,并支持语音播报,提供类似真人的接待体验。项目涵盖了环境准备、数据构建、代码实现、测试调优、部署等多个阶段,详细记录了开发过程中遇到的问题及解决方案,展示了系统在咨询公司信息、产品询问及招聘岗位咨询等场景下的应用潜力。未来计划在数据类型支持、会话记忆、并发处理、语音合成等方面进一步优化,以提升用户体验和服务质量。
339 0
|
Java Spring 容器
Java获取接口的所有实现类方法
这篇文章介绍了在Java中获取接口所有实现类的方法,包括使用JDK的ServiceLoader(SPI机制)和Spring Boot中的@Autowired自动注入及ApplicationContextAware接口两种方式。
1343 1
|
监控 安全 网络协议
这10款网络扫描工具,是个网工,都想全部安装!
这10款网络扫描工具,是个网工,都想全部安装!
2126 1
|
存储 数据采集 分布式计算
阿里巴巴数据仓库实践:从离线到实时的一体化探索
阿里巴巴的数据仓库实践从离线到实时的一体化探索,不仅为企业自身业务的快速发展提供了有力支撑,也为行业树立了标杆。通过不断优化技术架构、提升数据处理能力、加强数据治理和安全管理,阿里巴巴的实时数仓将为企业创造更大的价值,推动数字化转型的深入发展。未来,随着技术的不断进步和业务的持续拓展,阿里巴巴的实时数仓实践将展现出更加广阔的应用前景和发展空间。