写点什么

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

  • 2024-03-04
    福建
  • 本文字数:3434 字

    阅读完需:约 11 分钟

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

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



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


出现空指针异常的情况


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

当一个对象是 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 值。这也是阿里巴巴开发手册规约的建议。



  • 使用 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 风险



  • 借助工具扫描代码

在 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

体验地址:http://www.jnpfsoft.com/?from=001

用户头像

还未添加个人签名 2023-06-19 加入

还未添加个人简介

评论

发布
暂无评论
聊一聊日常开发中如何优雅的避免那无处不在的空指针异常_指针_快乐非自愿限量之名_InfoQ写作社区