首页 > 其他分享 >聊一聊日常开发中如何优雅的避免那无处不在的空指针异常

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

时间:2024-03-01 09:45:35浏览次数:31  
标签:String NPE 指针 无处不在 聊一聊 User null user

在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的博客,专注分享Java技术干货,包括Java基础、Spring Boot、Spring Cloud、Mysql、Redis、Elasticsearch、中间件、架构设计、面试题、程序员攻略等

标签:String,NPE,指针,无处不在,聊一聊,User,null,user
From: https://www.cnblogs.com/coderacademy/p/18044456

相关文章

  • C语言笔记11(指针二)
    指针变量作函数参数函数的参数不仅可以是整型、实型、字符型,还可以是指针型它的作用是讲一个变量的地址传送到另外一个函数中          数组名做函数参数当用数组名作为参数时,如果形参数组中元素的值发生变化,实参数组元素的值也随之变化若有一个实......
  • 电脑开机后出现黑屏且只显示鼠标指针
    电脑开机后,输入用户登录密码后,黑屏无法显示桌面,只有一个鼠标指针的情况下,多半是系统重要文件explorer.exe被杀毒软件拦截了,所以需要我们手动启动下该文件:按住Ctrl+Shift+Esc后弹出任务管理器后,点击左上角的“文件”,选择“运行新任务”,输入“C:\Windows\explorer.exe”或者“exp......
  • 关于指针和引用
    1.指针常量和常量指针2.在函数重载时,常量指针和常量引用类型的形参,与没有const的对应形参相比,被视为一种新的参数类型。因为常量指针和常量引用类型的const是具有所谓的“底层const”(即不可修改其指向的内容)3.引用是自带所谓的“表层const”的,而指针常量也是“表层const”的(即......
  • Rust的Deref特征:让智能指针“透明”的关键
    除了上篇文章中介绍过的Borrow和AsRef外,Rust中还有一个很常见的和引用相关的特征:Deref。不过,和Borrow、AsRef两个特征不同,Deref其实是用于重载解引用运算符(也就是*)的特征;在为某个类实现了Deref特征后,对它使用*运算就会调用特征中重载的方法。这篇文章不仅将介绍Deref特性,还将探......
  • 金三银四,聊一聊测试跳槽/面试的必备技能
    “金三银四”正是大厂春招旺季,最近霍格沃兹测试学社也是捷报频传,很多同学顺利拿下测试开发高薪Offer,但也有不少测试同学虽技术实力尚可,但在面试中却屡屡碰壁,铩羽而归。如何写能让面试官眼前一亮项目经验怎么写才能有理有据有亮点企业招聘模型及面试流程介绍面试技巧-教你高分......
  • c++引用和指针
    指针和引用当我们需要在程序中传递变量的地址时,可以使用指针或引用。它们都可以用来间接访问变量,但它们之间有一些重要的区别。指针是一个变量,它存储另一个变量的地址。通过指针,我们可以访问存储在该地址中的变量。指针可以被重新分配,可以指向不同的变量,也可以为NULL。指针使用*......
  • 指针
    目录一、什么是指针二、指针的定义三、向函数传递指针参数1.传入字符串类型的指针2.传入数组类型的指针3.传入切片一、什么是指针指针也是一种变量,但是它的功能只是用来存储变量内存地址(MemoryAddress),即指针变量的值为其他变量的内存地址指针是变量,所以它本身也有内存......
  • 掌握C语言指针,轻松解锁代码高效性与灵活性(下)
    ✨✨欢迎大家来到贝蒂大讲堂✨✨......
  • 掌握C语言指针,轻松解锁代码高效性与灵活性
    ✨✨欢迎大家来到贝蒂大讲堂✨✨......
  • Rust 智能指针
    本文在原文基础上有删减,原文参考Rust智能指针目录使用Box指向堆上的数据使用Box在堆上储存数据Box允许创建递归类型conslist的更多内容计算非递归类型的大小使用Box<T>给递归类型一个已知的大小通过Dereftrait将智能指针当作常规引用处理追踪指针的值像引用一样使用B......