如何避免 Java NPE(NullPointerException) 空指针问题

NPE(NullPointerException) 是在Java开发中常遇到的问题,特别对于刚入门的Java开发者来说,很容易忽略空指针的问题,进而影响整理代码质量。自己查阅了网络上相关资料,在这里对NPE空指针问题的解决方法做一个总结。

NPE的应对,整体上分为两大情况——1,接受外部数据,处理这些外部数据时,出现NPE;2,自己定义方法/接口时,空指针作为返回结果。

1、接受外部数据时避免出现NPE

有时候我们会将自己的代码封装成包或者服务,以供他人调用。外界调用我们代码时候传入的参数可能会不符合我们的预期,此时如果我们不对其进行检验,就有可能出现NPE的问题:例如,传入参数为空值,或者传入的请求没有某个字段。这种情况下,我们务必做好入参合法性检验,避免NPE报错的出现。以下是外部数据空指针检查处理的一些经验。

2、对方法入参进行 null 空值判断以及报错

防止 null 值入参对我们代码程序的影响,最直接的方式就是对入参进行 null 判断,对不符合预期的 null 值进行特殊处理(抛出异常Exception,输出相应日志等)。

我们可以使用Objects.requireNonNull(x) 进行空值检查,当参数为 null,则会抛出异常。我们也可以在方法声明时,在参数前加入@NotNull修饰符,例如public void foo(@NotNull int x)。这样在进行开发时,IdeaJ 会对明显的空值输入提示,从而减少NPE发生可能。进一步,我们可以使用 lombok 包中的 @NonNull 修饰符同时完成上述两点,和 @NotNull 一样用法,在编译时 lombok 会在方法开头自动生成空值检测的代码,此外 IdeaJ 也会有相应的入参空值提醒。

需要注意的是,我们的代码作为模块被别人开发调用时,入参空值报错是可行的,但是不是所有情况抛出异常都是合适的,如果我们的代码是线上作为服务被别人调用,我们可以进行日志输出和相应的空值逻辑处理,而非简单的抛出异常,以保证我们线上服务状态的正常。

3、对get方法返回的值多加小心

常常我们需要从某个数据对象获取某个字段值,比如从一个pojo类中get某个字段,从map中拿到key对应的value,或者是从上游请求传来的jsonobject中获取某个字段值。有时候疏忽了这些数据对象隐含的null值可能性,会导致NPE问题。例如,Map类的get方法在key不存在时,会返回null;同样的,fastjson中JSONObect类的get相关方法(e.g. getString, getArray)也会在给定字段不存在时返回null。

一个简单的经验准则,当方法名有get时,有意识考虑这个get出来的值会不会是空值null,会不会导致空指针的问题。

4、判断null

4.1、直接判断null的写法

在进行 null 判断的时候,我经常看到这样的写法 if(null == yourObj),为什么要把null写在判断表达式的前方?我在网上查阅了一下相关资料,这样写的原因主要是防止在 if 语句中相等判断运算符==误写成赋值运算符=,在Java 1.5以前,如果将if(yourObj == null)写成 if(yourObj = null)在编译时,是不会报错的。虽然在Java 1.5以后 if语句要求表达式值为布尔 boolean 值,但if(null == yourObj)的代码习惯一直延续下来。

此外,如果你对代码可读性要求更高,我们可以用 Objects.nonNull()(或者Objects.isNull())来判断一个对象是否为空值。

4.2、连续判断null情况下一种简洁写法

在解析json时,如果要取的字段是多层级嵌套在里层的,会遇到连续判断空指针的情况,如果在if里面嵌套if语句会影响代码可读性,一种办法是使用Optional解决这个问题。具体的例子如下:

adam.getInfo().getHeight() // 180
bob.getInfo().getHeight()  // throw NPE,因为info为null
david.getInfo().getHeight() // null

//Optional.of(180)
Optional.ofNullable(adam.getInfo()).map(Person::getInfo).map(Info::getHeight)

//Optional.empty  即使 info 为空值null,也不会报错
Optional.ofNullable(bob.getInfo()).map(Person::getInfo).map(Info::getHeight)

//Optional.empty  最后的 null 也会转换成 Optional.empty,这也可以避免下游使用 height 时候潜在的 NPE 报错
Optional.ofNullable(david.getInfo()).map(Person::getInfo).map(Info::getHeight)

4.3、一些空值判断的 Helper 方法

当我们在判断字符串类型 String 变量为空时候,有时候null值和空字符串“”会用相同的业务逻辑去处理,我们可以利用 org.apache.commons.lang3 中辅助类StringUtilsisEmpty()进行字符串为“空”的判断,该方法在字符串为null值或空字符串时都会返回true。更进一步的,StringUtilsisBlank()在上面两种情况外,在字符串只有空格的情况也返回true。

类似的,在判断集合类为空时候,null值与空集合可能有相同的处理逻辑,我们也可以利用org.apache.commons.lang3 中辅助类 CollectionUtilsisEmpty() 同时判断变量是否为null值或者空元素集合。

这里附上org.apache.commons.lang3 类的pom引用片段,方便大家参考

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.12.0</version>
</dependency>

5、处理字符串时一些避免NPE的技巧

在进行字符串String相关操作时候,有两个简单的技巧可能避免潜在的空指针问题。

一是,在字符串比较时,如果是变量和一个常量比较,使用常量的equals方法,例

// 如果 name 为 null,将会报错
if (name.equals("Bob")) {
        ...
}

// 可以避免 name 为 null 的情况。通常业务开发情形下,我们不会认为null与某个常量是相等的。
if("Bob".equals(name)) {
        ...
}

如果是两个String变量比较,而且这两个变量都有可能为null值,不论使用哪个变量的equals()方法都有可能造成潜在的NPE错误。这种情况下,我们可以使用Objects.equals(x, y)方法进行值相等的判断。Objects.equals方法有一个地方需要特别注意,如果进行比较的两个变量都为null,会返回true,该返回的true值是否符合逻辑,需要在业务场景下判断。

二是,将其它类型变量转换成String时,用 String.valueOf(foo) 而非 foo.toString()

name.toString()  // 如果 name 为 null,会报NPE错误

String.valueOf(name)   // 如果 name 为 null,不会报NPE错误,会返回字符串 "null"

6、自己定义方法时 null 的返回

NPE出现的根本原因是开发者忽略了潜在的空值,那么我们在写自己方法时候,就尽量避免返回null值,从源头上解决NPE问题的出现。

7、用其它表示空的值替代null

首先,我们可以考虑一下自己所写的方法是否真的有必要返回null值,方法返回null值的意义是什么,是否可以用其它表示“空”含义的值替换null。例如,如果方法声明返回的是一个List,我们可以考虑返回空List Collections.emptyList() 而非null;同样的,如果方法要返回的是String,我们也可以考虑使用空字符""来替代null。

8、null无法被替换的情况

当然,并非一味替换null就是合适的,我们需要结合具体业务场景,例如,当你去解析外部服务的请求结果,并返回一个List时,可能你需要用null去表达服务调用失败,来避免混淆实际返回结果为空List的情况。

在这些业务场景下,null值有着无法被替代的意义,返回null可能是最好的解决方案,这时也有一些方法可以避免潜在的NPE错误。

  1. 返回 Optional 显式地声明可能存在的空值。将方法要返回null的地方改为返回 Optional.empty()。当自己或其他开发者在调用该方法时,会被强制要求考虑返回值可能为空的情况,从而避免NPE的出现。具体关于Optional的介绍,可以参考https://www.baeldung.com/java-optional
  2. 在可能返回空值的方法上方加入 @Nullable 修饰,这样IDEA会在该方法调用处提醒处理可能的空值。
@Nullable
public static String foo (String x) {
    return null;
}

1、使用空对象设计模式,这个方法可能不太常用,这里不作赘述,具体可以参考

  空对象模式 | 菜鸟教程

小结

  1. 接收外部数据时候,需意识到潜在null值的出现。方法名有get时候,考虑到返回值可能为null
  2. 字符串操作时,foo.equals("some string")替换为 "some string".equals(foo) ,foo.toString() 替换为 String.valueOf(foo)
  3. 可以利用Optional.ofNullable(...).map(...).map(...).orElse()的写法处理多层级嵌套时,get中出现null的情况
  4. 开发新方法时,如果返回List,可以考虑返回空列表Collections.emptyList()来替代返回null
  5. 开发新方法时,如果返回String,可以考虑返回空字符串来替代返回null
  6. 开发新方法时,如果确实需要返回null,可以考虑使用Optional显式声明空值
  7. StringUtils.isEmpty() 可以同时判断字符串为null或空字符串“”的情况,StringUtils.isBlank() 还可以判断字符串只包含空格的情况
  8. CollectionUtils.isEmpty() 可以判断一个集合为null或空元素集合的情况
  • 22
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
Lambda表达式在处理集合元素时,有可能会遇到空指针异常NullPointerException)的问题。当对一个集合进行stream操作时,如果集合中的元素存在空值,那么在Lambda表达式中使用该元素的属性或方法时就会抛出空指针异常。 在你提供的代码中,当使用Lambda表达式中的`o.getId()`时,如果`o`的`id`属性为空,会抛出空指针异常。正常情况下,`list`不为空,最多会抛出`NumberFormatException`,但在这里抛出的却是空指针异常。 要解决这个问题,你可以在Lambda表达式中添加空值判断。例如,可以使用`Optional`类来对`o.getId()`进行空值检测,避免抛出空指针异常。可以使用类似下面的代码来处理: ``` list.stream() .map(o -> Optional.ofNullable(o.getId()).map(Long::valueOf).orElse(null)) .collect(Collectors.toList()); ``` 通过使用`Optional.ofNullable`方法来包装`o.getId()`,如果`o.getId()`为空,就返回`null`,否则将其转换为`Long`类型。这样做可以避免空指针异常的发生,让代码更加健壮。 引用: - lambda表达式中list.stream().map(o -> Long.valueOf(o.getId())).collect(Collectors.toList());报出了NPE,正常情况下list不为空不会发生NPE,最多o.getId()中id为空Long.valueOf(null)为空抛出NumberFormatException,但是最终却是因为id为空抛出了NPE。 - 此时为空指针异常NPE,而不是NumberFormatException - 本教程为授权出品教程 本套视频涵盖了 Java8 的新特性:Lambda表达式、强大的 Stream API、全新时间日期 API、...Java8 的新特性使 Java 的运行速度更快、代码更少(Lambda 表达式)、便于并行、最大化减少空指针异常。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [lambda表达式中奇怪的NullPointerException异常](https://blog.csdn.net/loveyour_1314/article/details/121668039)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *3* [通俗易懂的Java8新特性教程(含配套资料)](https://download.csdn.net/download/weixin_26875051/19651829)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

€☞扫地僧☜€

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值