聊一聊日常开发中如何优雅的避免那无处不在的空指针异常

news2024/11/17 7:20:24

在Java编程语言中,NullPointerException(简称NPE)是一种常见的运行时异常,当程序试图访问或操作一个还未初始化(即值为null)的对象引用时,Java虚拟机就会抛出NullPointerException。如果我们在日常开发中,不能很好的去规避NPE,那么可能因为数据或者其他问题就会导致线上问题。。。很烦。。。。

阿里巴巴开发手册规约中也说明防止NPE,是程序员的基本素养。。。

image.png

接下来我们先谈谈几种可能会出现空指针异常的方式。

出现空指针异常的情况

  • 访问空对象的属性或调用空对象的方法

当一个对象是null时,试图访问一个对象的属性或调用其方法,就会触发空指针异常。

String text = null;
int length = text.length();

User user = null;
String userName = user.getUserName();

  • 数组为null或者数组元素为null

当尝试访问数组中的某个索引处的元素,而该元素为null时,同样会导致空指针异常。

String[] strs = null;
int length = strs.length;

String[] strs = new String[3];  
int length = strs[2].length();

  • 集合中null元素访问

当集合中存在null元素,当我们遍历集合,访问到这个元素的属性或者方法时也会抛出NPE,这种情况也会出现在我们的日常开发中,有时候就会因为数据问题导致这种情况发生,常常也莫名其妙。。。。

List<String> list = Lists.newArrayList();  
list.add(null);  
System.out.println(list.get(0).length());

  • 调用的方法返回null

调用某个方法,期望其返回一个非null的对象,但实际返回了null。当然这种情况等同于访问空对象的属性或者方法。这在实际开发过程中极易出现的一种情况。比如我们使用Mybatis从数据库中查询一条记录时,数据不存在,就会返回null。这种情况尤为注意。

private User getUserInfo(){  
    return null;  
}

User user = getUserInfo();
String userName = user.getUserName();

  • 使用基本数据类型的包装类

在使用基本数据类型的包装类时,如果未正确初始化,再转成int时,可能导致空指针异常。

Integer i = null;  
int num = i;

以上大概是我想到或者常遇到的一些可能会发生NPE的情况,如果还有其他情况,可以贴出来讨论。

那么我们该如何避免NPE呢?

避免NPE的几种方式

  • 访问对象前要谨慎

在使用对象之前,始终检查它是否为null。这包括方法参数、返回值以及对象的属性。在访问对象的方法或属性之前,使用条件语句判断对象是否为null。比如我们在访问User对象前,一定要判null

User user = new User();  
if (user != null){  
    String userName = user.getUserName();  
    Address address = user.getAddress();  
    if (address != null){  
        String coutry = address.getCountry();  
    }  
}

或者我们的user是从一个方法中获取的,例如数据库中查询,那么我们在访问这个对象前,一定要判null,如果为null要抛出对应的业务异常,然后我们就可以在接口响应中对应返回错误的信息即可,此时就算是一个正常的流程了。这点尤为重要,一定要注意。

User user = userManager.getUserById(Long userId);
if (user == null){
	throw new ServiceException(""当前查询的对象不存在);
}

关于SpringBoot项目中捕获自定义业务异常,统一异常管理,统一结果返回,可以参考这篇文章:SpringBoot统一结果返回,统一异常处理,大牛都这么玩 | 码农Academy的博客

当然如果使我们在写User getUserById(Long id)返回对象或者List<User> listUserByIds(List<Long> idList)时我们可以不返回null,可以返回一个对象默认信息或者一个空集合,这样调用方就不会出现NPE风险,当然我们不强制返回一个对象或者空集合,但是必须添加注释充分 说明什么情况下会返回null值。这也是阿里巴巴开发手册规约的建议。

image.png

  • 使用Optional类

JDK8以上版本提供了Optional类,它是一个容器对象,可用于包装可能为null的值。我们可以使用它判断null问题,同时也解决了多层级访问问题,配合使用orElse时,会先执行orElse方法,然后执行逻辑代码,不管是否出现了空指针。

String country = Optional.ofNullable(user)  
        .map(User::getAddress)  
        .map(Address::getCountry)  
        .orElse("");

String country = Optional.ofNullable(user)  
        .map(User::getAddress)  
        .map(Address::getCountry)  
        .orElseGet(() -> defaultContry());

private String defaultContry(){
	return "CN";
}

我们还可以使用orElseThrow()方法,当Optional中的对象是一个null时我们直接抛出异常:

String userName = Optional.ofNullable(user).map(User::getUserName).orElseThrow(() -> new ServiceException("当前用户信息不存在"));

  • 使用断言避免空指针

使用Java断言(assert)来检查变量是否为null。但要注意,断言通常在开发和测试阶段启用,而在生产环境中可能被禁用(在生产环境中,通常不会启用断言以避免不必要的性能开销以及防止潜在的错误信息泄漏)。

User user = new User();
assert user != null : "user should not be null";
Address address = user.getAddress();  
assert address != null : "address should not be null";  
String coutry = address.getCountry();  

  • 使用@Nullable注解

使用javax.annotation.Nullable注解,@Nullable注解通常用于标记一个方法的参数、返回值或者字段可能为null。这个注解并非Java标准库的一部分,但在一些第三方库(如JSR 305库中的javax.annotation.Nullable,以及Google Guava和JetBrains的Kotlin标准库等)中广泛使用,并且被许多IDE和静态分析工具支持。以便在编译期或开发工具中提示可能的NPE风险。

@Nullable  
private static User getUserById(Long userId){  
    return null;  
}  
  
private static void handlerUser(@Nullable User user){  
    System.out.println(user.getUserName());  
}

public static void main(String[] args) {  
  Long userId = 0L;  
  User user = getUserById(userId);  
  String userName = user.getUserName();  
  handlerUser(user);  
}

此时IDEA就会警告会出现NPE风险

image.png

  • 借助工具扫描代码

在Java开发中,我们还可以使用以下工具扫描代码以发现潜在的空指针异常风险。

  1. IntelliJ IDEA:内置了强大的静态代码分析器,能够检测出可能的NPE和其他代码问题。

  2. SonarQube / SonarLint:提供持续集成和本地IDE插件形式的静态代码分析,能找出潜在的空指针以及其他质量或安全问题。Sonar可以定时扫描仓库中的代码,可以发现代码中的一些潜在风险,可以通过一些通知例如邮件等告知代码提交者这段代码的风险。

  3. FindBugs(现更名为SpotBugs):另一个开源的静态分析工具,能够发现潜在的bug,包括可能导致NPE的情况。

  4. 阿里巴巴Java开发规约插件: 对于Eclipse和IntelliJ IDEA都有相应的插件版本,基于阿里巴巴内部Java编码规范,包含了对可能出现NPE情况的检测。

补充一点

在JDK 17中引入的Helpful NullPointerExceptions特性确实增强了空指针异常信息的准确性与可用性。当发生NullPointerException时,JVM现在能够提供更精确的位置信息,特别是在链式调用场景下,它会指出导致空指针异常的具体对象引用。这有助于开发者更快地定位到代码中的问题所在,无需通过堆栈跟踪逐层分析来判断哪个对象引用为null。假如我们访问user.getAddress().getCountry().length()时,在JDK17以前,如果发生了空指针异常,他只会打印出来发生了空指针异常,但是并没有告知到底是user对象还是address对象还是coutnry发生了异常:

Exception in thread "main" java.lang.NullPointerException
	at com.study.base.core.base.NpeTest.main(NpeTest.java:23)

但是在JDK17以后,借助Helpful NullPointerExceptions特性,异常信息将更加精确,可能会类似打印这样的信息,精确到那个值发生了空指针异常:

Exception in thread "main" java.lang.NullPointerException: Cannot invoke "Address.getCountry()" because "user.address" is null
	at com.study.base.core.base.NpeTest.main(NpeTest.java:23)

这又多了一个升级JDK到17以上的理由。

结论

NullPointerException(NPE)是Java开发中常见的运行时异常,源于对未初始化或已置为null的对象引用进行操作。在实际开发过程中,进行非空检查、使用Optional类以及采用Null安全注解以及使用检查工具等策略可以有效避免此类异常的发生。

文章转载自:码农Academy

原文链接:https://www.cnblogs.com/coderacademy/p/18044456

体验地址:引迈 - JNPF快速开发平台_低代码开发平台_零代码开发平台_流程设计器_表单引擎_工作流引擎_软件架构

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1488832.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

GO 的 Web 开发系列(六)—— 遍历路径下的文件

文件 IO 处理是程序的基础功能&#xff0c;WEB 程序中通过文件 IO 实现附件的上传与下载。在 GO 中&#xff0c;有多种方式可以遍历文件目录&#xff0c;获取文件路径&#xff0c;本文从使用层面上论述这些函数。 预先准备一个包含子目录的目录&#xff0c;用于遍历测试&#…

YOLOv5-Openvino和ONNXRuntime推理【CPU】

1 环境&#xff1a; CPU&#xff1a;i5-12500 Python&#xff1a;3.8.18 2 安装Openvino和ONNXRuntime 2.1 Openvino简介 Openvino是由Intel开发的专门用于优化和部署人工智能推理的半开源的工具包&#xff0c;主要用于对深度推理做优化。 Openvino内部集成了Opencv、Tens…

政安晨【TypeScript高级用法】(一):类与对象

为了在今后使用Cocos引擎开发虚拟场景&#xff0c;咱们首先要了解TypeScript语言。 政安晨的个人主页&#xff1a;政安晨 欢迎 &#x1f44d;点赞✍评论⭐收藏 收录专栏: AI虚拟世界大讲堂 希望政安晨的博客能够对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提…

NestJS核心成员组成-中间件

关于MiddleWare 从本质来说&#xff0c;我们使用NestJS的时候&#xff0c;有百分之九十的原因是我们只想要一个能提供API的服务&#xff0c;即便是一个简单的由数据库https请求的后台&#xff0c;也能满足百分之九十的人了。 对于有Express以及Koa开发的朋友来说&#xff0c;…

java中的map集合

1.jdk8 Map接口实现类的特点&#xff1a; ①Map与Collection并列存在&#xff0c;用于保存具有映射关系的数据&#xff1a;Key-Value; ②Map中的key与value可以是任何引用类型的数据&#xff0c;会封装到HashMap$Node对象中&#xff1b; ③Map中的key不允许重复&#xff0c;…

【计算机网络】IO多路转接之poll

文章目录 一、poll函数接口二、socket就绪条件三、poll的优点四、poll的缺点五、poll使用案例--只读取数据的server服务器1.err.hpp2.log.hpp3.sock.hpp4.pollServer.hpp5.main.cc 一、poll函数接口 #include <poll.h> int poll(struct pollfd *fds, nfds_t nfds, int t…

YOLOv9独家原创改进|使用可改变核卷积AKConv改进RepNCSPELAN4

专栏介绍&#xff1a;YOLOv9改进系列 | 包含深度学习最新创新&#xff0c;主力高效涨点&#xff01;&#xff01;&#xff01; 一、改进点介绍 AKConv是一种具有任意数量的参数和任意采样形状的可变卷积核&#xff0c;对不规则特征有更好的提取效果。 RepNCSPELAN4是YOLOv9中的…

Pytorch学习 day03(Tensorboard)

Tensorboard Tensorboard能够可视化loss的变化过程&#xff0c;便于我们查看模型的训练状态&#xff0c;也能查看模型当前的输入和输出结果 在Pycharm中&#xff0c;可以通过按住ctrl&#xff0c;并左键点击某个库来进入源文件查看该库的使用方法 SummaryWriter是用来向log_di…

【计算机操作系统】操作系统的诞生

目录 相关概念&#xff1a; 一、手工操作阶段 二、脱机输入/输出阶段 &#xff08;1&#xff09;脱机输入技术 &#xff08;2&#xff09;脱机输出技术 三、单道批处理阶段 四、多道批处理阶段 五、分时技术产生 六、实时系统产生 相关概念&#xff1a; ① 作业&#…

数据结构从入门到精通——链表

链表 前言一、链表1.1 链表的概念及结构1.2 链表的分类1.3 链表的实现1.4 链表面试题1.5 双向链表的实现 二、顺序表和链表的区别三、单项链表实现具体代码text.htext.cmain.c单链表的打印空间的开辟链表的头插、尾插链表的头删、尾删链表中元素的查找链表在指定位置之前、之后…

【C++基础】STL容器面试题分享||上篇

&#x1f308;欢迎来到C基础专栏 &#x1f64b;&#x1f3fe;‍♀️作者介绍&#xff1a;前PLA队员 目前是一名普通本科大三的软件工程专业学生 &#x1f30f;IP坐标&#xff1a;湖北武汉 &#x1f349; 目前技术栈&#xff1a;C/C STL 1.请说说 STL 的基本组成部分2.详细的说&…

SwiftUI 在 App 中弹出全局消息横幅(下)

功能需求 在 SwiftUI 开发的 App 界面中,有时我们需要在全局层面向用户展示一些消息: 如上图所示:我们弹出的全局消息横幅位于所有视图之上,这意味这它不会被任何东西所遮挡;而且用户可以点击该横幅关闭它。这是怎么做到的呢? 在本篇博文中,您将学到以下内容 功能需求…

C++/WinRT教程(第三篇)API的使用

目录 前言 Windows API 在WinRT中的投影 C/WinRT的头文件&#xff08;投影标头&#xff09; 通过对象、接口或通过 ABI 访问成员 投影类型的初始化方法 不要错误地使用延迟初始化 不要错误地使用复制构造 使用 winrt::make 进行构造 标准构造方法 在WinRT组件中实现A…

Linux--文件(2)-重定向和文件缓冲

命令行中的重定向符号 介绍和使用 在Linux的命令行中&#xff0c;重定向符号用于将命令的输入或输出重定向到文件或设备。 常见的重定向符号&#xff1a; 1.“>“符号&#xff1a;将命令的标准输出重定向到指定文件中&#xff0c;并覆盖原有的内容。 2.”>>“符号&a…

C/C++工程师面试题(STL篇)

STL 中有哪些常见的容器 STL 中容器分为顺序容器、关联式容器、容器适配器三种类型&#xff0c;三种类型容器特性分别如下&#xff1a; 1. 顺序容器 容器并非排序的&#xff0c;元素的插入位置同元素的值无关&#xff0c;包含 vector、deque、list vector&#xff1a;动态数组…

[Unity3d] 网络开发基础【个人复习笔记/有不足之处欢迎斧正/侵删】

TCP/IP TCP/IP协议是一 系列规则(协议)的统称&#xff0c;他们定义了消息在网络间进行传输的规则 是供已连接互联网的设备进行通信的通信规则 OSI模型只是一个基本概念,而TCP/IP协议是基于这个概念的具体实现 TCP和UDP协议 TCP:传输控制协议&#xff0c;面向连接&#xff0c…

面试经典150题——简化路径

"A goal is a dream with a deadline." - Napoleon Hill 1. 题目描述 2. 题目分析与解析 2.1 思路一 这个题目开始看起来并不太容易知道该怎么写代码&#xff0c;所以不知道什么思路那就先模拟人的行为&#xff0c;比如对于如下测试用例&#xff1a; 首先 /代表根…

YOLOv8姿态估计实战:训练自己的数据集

课程链接&#xff1a;https://edu.csdn.net/course/detail/39355 YOLOv8 基于先前 YOLO 版本的成功&#xff0c;引入了新功能和改进&#xff0c;进一步提升性能和灵活性。YOLOv8 同时支持目标检测和姿态估计任务。 本课程以熊猫姿态估计为例&#xff0c;将手把手地教大家使用C…

使用TensorRT-LLM进行生产环境的部署指南

TensorRT-LLM是一个由Nvidia设计的开源框架&#xff0c;用于在生产环境中提高大型语言模型的性能。该框架是基于 TensorRT 深度学习编译框架来构建、编译并执行计算图&#xff0c;并借鉴了许多 FastTransformer 中高效的 Kernels 实现&#xff0c;并且可以利用 NCCL 完成设备之…

深入理解nginx的https alpn机制

目录 1. 概述2. alpn协议的简要理解2.1 ssl的握手过程2.2 通过抓包看一下alpn的细节3. nginx源码分析3.1 给ssl上下文设置alpn回调3.2 连接初始化3.3 处理alpn协议回调3.4 握手完成,启用http协议4.4 总结阅读姊妹篇:深入理解nginx的https alpn机制 1. 概述 应用层协议协商(…