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

打印 上一主题 下一主题

主题 860|帖子 860|积分 2580

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

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


  • 访问空对象的属性或调用空对象的方法
    当一个对象是null时,试图访问一个对象的属性或调用其方法,就会触发空指针非常。
  1. String text = null;
  2. int length = text.length();
  3. User user = null;
  4. String userName = user.getUserName();
复制代码

  • 数组为null或者数组元素为null
    当实行访问数组中的某个索引处的元素,而该元素为null时,同样会导致空指针非常。
  1. String[] strs = null;
  2. int length = strs.length;
  3. String[] strs = new String[3];  
  4. int length = strs[2].length();
复制代码

  • 集合中null元素访问
    当集合中存在null元素,当我们遍历集合,访问到这个元素的属性或者方法时也会抛出NPE,这种环境也会出现在我们的日常开发中,有时候就会因为数据问题导致这种环境发生,常常也莫名其妙。。。。
  1. List<String> list = Lists.newArrayList();  
  2. list.add(null);  
  3. System.out.println(list.get(0).length());
复制代码

  • 调用的方法返回null
    调用某个方法,期望其返回一个非null的对象,但现实返回了null。当然这种环境等同于访问空对象的属性或者方法。这在现实开发过程中极易出现的一种环境。比如我们使用Mybatis从数据库中查询一条记录时,数据不存在,就会返回null。这种环境尤为注意。
  1. private User getUserInfo(){  
  2.     return null;  
  3. }
  4. User user = getUserInfo();
  5. String userName = user.getUserName();
复制代码

  • 使用根本数据类型的包装类
    在使用根本数据类型的包装类时,假如未正确初始化,再转成int时,可能导致空指针非常。
  1. Integer i = null;  
  2. int num = i;
复制代码
以上大概是我想到或者常遇到的一些可能会发生NPE的环境,假如还有其他环境,可以贴出来讨论。
那么我们该如何避免NPE呢?
避免NPE的几种方式


  • 访问对象前要谨慎
    在使用对象之前,始终查抄它是否为null。这包括方法参数、返回值以及对象的属性。在访问对象的方法或属性之前,使用条件语句判定对象是否为null。比如我们在访问User对象前,肯定要判null
  1. User user = new User();  
  2. if (user != null){  
  3.     String userName = user.getUserName();  
  4.     Address address = user.getAddress();  
  5.     if (address != null){  
  6.         String coutry = address.getCountry();  
  7.     }  
  8. }
复制代码
或者我们的user是从一个方法中获取的,例如数据库中查询,那么我们在访问这个对象前,肯定要判null,假如为null要抛出对应的业务非常,然后我们就可以在接口响应中对应返回错误的信息即可,此时就算是一个正常的流程了。这点尤为紧张,肯定要注意。
  1. User user = userManager.getUserById(Long userId);
  2. if (user == null){
  3.         throw new ServiceException(""当前查询的对象不存在);
  4. }
复制代码
关于SpringBoot项目中捕捉自定义业务非常,统一非常管理,统一结果返回,可以参考这篇文章:SpringBoot统一结果返回,统一非常处理,大牛都这么玩 | 码农Academy的博客
当然假如使我们在写User getUserById(Long id)返回对象或者List listUserByIds(List idList)时我们可以不返回null,可以返回一个对象默认信息或者一个空集合,这样调用方就不会出现NPE风险,当然我们不强制返回一个对象或者空集合,但是必须添加解释充分 阐明什么环境下会返回null值。这也是阿里巴巴开发手册规约的建议。


  • 使用Optional类
    JDK8以上版本提供了Optional类,它是一个容器对象,可用于包装可能为null的值。我们可以使用它判定null问题,同时也解决了多层级访问问题,配合使用orElse时,会先执行orElse方法,然后执行逻辑代码,不管是否出现了空指针。
  1. String country = Optional.ofNullable(user)  
  2.         .map(User::getAddress)  
  3.         .map(Address::getCountry)  
  4.         .orElse("");
  5. String country = Optional.ofNullable(user)  
  6.         .map(User::getAddress)  
  7.         .map(Address::getCountry)  
  8.         .orElseGet(() -> defaultContry());
  9. private String defaultContry(){
  10.         return "CN";
  11. }
复制代码
我们还可以使用orElseThrow()方法,当Optional中的对象是一个null时我们直接抛出非常:
  1. String userName = Optional.ofNullable(user).map(User::getUserName).orElseThrow(() -> new ServiceException("当前用户信息不存在"));
复制代码

  • 使用断言避免空指针
    使用Java断言(assert)来查抄变量是否为null。但要注意,断言通常在开发和测试阶段启用,而在生产环境中可能被禁用(在生产环境中,通常不会启用断言以避免不必要的性能开销以及防止潜在的错误信息走漏)。
  1. User user = new User();
  2. assert user != null : "user should not be null";
  3. Address address = user.getAddress();  
  4. assert address != null : "address should not be null";  
  5. String coutry = address.getCountry();  
复制代码

  • 使用@Nullable注解
    使用javax.annotation.Nullable注解,@Nullable注解通常用于标志一个方法的参数、返回值或者字段可能为null。这个注解并非Java标准库的一部分,但在一些第三方库(如JSR 305库中的javax.annotation.Nullable,以及Google Guava和JetBrains的Kotlin标准库等)中广泛使用,并且被许多IDE和静态分析工具支持。以便在编译期或开发工具中提示可能的NPE风险。
  1. @Nullable  
  2. private static User getUserById(Long userId){  
  3.     return null;  
  4. }  
  5.   
  6. private static void handlerUser(@Nullable User user){  
  7.     System.out.println(user.getUserName());  
  8. }
  9. public static void main(String[] args) {  
  10.   Long userId = 0L;  
  11.   User user = getUserById(userId);  
  12.   String userName = user.getUserName();  
  13.   handlerUser(user);  
  14. }
复制代码
此时IDEA就会警告会出现NPE风险


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

  • IntelliJ IDEA:内置了强盛的静态代码分析器,能够检测出可能的NPE和其他代码问题。
  • SonarQube / SonarLint:提供连续集成和本地IDE插件形式的静态代码分析,能找出潜在的空指针以及其他质量或安全问题。Sonar可以定时扫描仓库中的代码,可以发当代码中的一些潜在风险,可以通过一些关照例如邮件等告知代码提交者这段代码的风险。
  • FindBugs(现更名为SpotBugs):另一个开源的静态分析工具,能够发现潜在的bug,包括可能导致NPE的环境。
  • 阿里巴巴Java开发规约插件: 对于Eclipse和IntelliJ IDEA都有相应的插件版本,基于阿里巴巴内部Java编码规范,包含了对可能出现NPE环境的检测。
补充一点

在JDK 17中引入的Helpful NullPointerExceptions特性确实加强了空指针非常信息的准确性与可用性。当发生NullPointerException时,JVM现在能够提供更精确的位置信息,特别是在链式调用场景下,它会指出导致空指针非常的具体对象引用。这有助于开发者更快地定位到代码中的问题所在,无需通过堆栈跟踪逐层分析来判定哪个对象引用为null。
假如我们访问user.getAddress().getCountry().length()时,在JDK17以前,假如发生了空指针非常,他只会打印出来发生了空指针非常,但是并没有告知到底是user对象还是address对象还是coutnry发生了非常:
  1. Exception in thread "main" java.lang.NullPointerException
  2.         at com.study.base.core.base.NpeTest.main(NpeTest.java:23)
复制代码
但是在JDK17以后,借助Helpful NullPointerExceptions特性,非常信息将更加精确,可能会类似打印这样的信息,精确到那个值发生了空指针非常:
  1. Exception in thread "main" java.lang.NullPointerException: Cannot invoke "Address.getCountry()" because "user.address" is null
  2.         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、中间件、架构设计、面试题、程序员攻略等

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

何小豆儿在此

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表