前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >避开NullPointerException的10条建议

避开NullPointerException的10条建议

作者头像
草捏子
发布2020-08-10 12:09:10
2K0
发布2020-08-10 12:09:10
举报
文章被收录于专栏:草捏子草捏子

1. 引言

NullPointerException应该是 Java 开发中最常出现的问题,也是 Java 程序员最容易犯的错误。虽然看起来是个小错误,但带来的影响却不小,Tony Hoare(null 引用的发明者)在 2009 年说过 NPE 大约给企业造成数十亿美元的损失。在这工作半年内,我就踩了好几次 NPE 的坑。举个例子,我需要在原有逻辑上加一段代码,而新加的代码报错抛出了 NPE,同时又没做异常处理,就直接导致后面的逻辑不运行了,影响了整个原有逻辑,太恐怖了。所以大家一定要小心避开 NPE 这个坑。

本文将会从以下两个方面说起:

  1. 发生 NPE 的可能情况
  2. 避开 NPE 的建议

2. 发生 NPE 的可能情况

首先我们需要清楚 NPE 是怎么发生的。

代码语言:javascript
复制
String s;
String[] ss;

当声明一个引用变量时,若未指定其指向的内容,Java 会将其默认指向 null,一个空地址,意味着“什么都没有指向”。后续若也没有为该变量赋值,则当使用这个变量里的内容时,便会抛出 NPE。

例如通过 .去访问方法或者变量, []去访问数组插槽:

代码语言:javascript
复制
System.out.println(s.length());
System.out.println(ss[0]);

以下是 NPE 的 Javadoc 概述的 6 个可能发生情况

  1. 在空对象上调用实例方法。对空对象调用静态方法或类方法时,不会报 NPE,因为静态方法不需要实例来调用任何方法;
  2. 访问或更改空对象上的任何变量或字段时
  3. 抛出异常时抛出 null
  4. 数组为 null 时,访问数组长度
  5. 数组为 null 时,访问或更改数组的插槽
  6. 对空对象进行同步或在同步块内使用 null

3. 避开 NPE 的建议

这节将介绍如何在开发过程中避开 NPE 的一些建议。

(1)尽量避免在未知对象上调用 equals() 方法和 equalsIgnoreCase() 方法,而是在已知的字符串常量上调用

由于 equals()equalsIgnoreCase() 具有对称性,所以可以直接翻转,这是很容易实现的。

代码语言:javascript
复制
Object unknowObject = null;
if (unknowObject.equals("knowObject")) {
    System.out.println("如果 unknowObject 是 null,则会抛出 NPE");
}
if ("knowObject".equals(unknowObject)) {
    System.out.println("避免 NPE");
}

(2)避免使用 toString(),而是 String.valueOf()

这是因为 String.valueOf() 中做了非空校验,同样里面也调用了对象的 toString()方法,所以结果是相同的。

代码语言:javascript
复制
Object unknowObject = null;
System.out.println(unknowObject.toString());
System.out.println(String.valueOf(unknowObject));

(3)使用 null 安全的方法和库

开源库的方法通常都了非空校验,例如 Apache common 库中的 StringUtils 工具类中的 isBlank()isNumeric() 等方法,使用时不必担心 NPE。那我们在使用第三方库时,一定要了解它是否是 null 安全的,如果不是,则需要我们自己做好非空校验。

代码语言:javascript
复制
System.out.println(StringUtils.isBlank(null));
System.out.println(StringUtils.isNumeric(null));

(4)当方法返回集合或数组时,避免返回 null,而应是空集合或空数组

返回空集合或空数组时,可以保证调用方法(如 size()length())不会出现 NPE。而且 Collections 类中提供了方便的空 List、Set和Map, Collections.EMPTY_LISTCollections.EMPTY_SetCollections.EMPTY_MAP

代码语言:javascript
复制
public List fun(Customer customer){
   List result = Collections.EMPTY_LIST;
   return result;
}

(5)使用 @NotNull 和 @Nullable 注解

  • @NonNull可以标注在方法、字段、参数之上,表示对应的值不可以为空
  • @Nullable可以标注在方法、字段、参数之上,表示对应的值可以为空

以上两个注解在程序运行的过程中不会起任何作用,只会在IDE、编译器、FindBugs检查、生成文档的时候提示。

有好几种 @NotNull@Nullable,我还没能搞明白,具体怎么使用我先不讲了。但即使不谈检测,单纯作为标识也是能够起到文档的作用。

(6)避免不必要的装箱拆箱

如果包装对象为 null,在拆箱时容易发生 NPE。

代码语言:javascript
复制
Integer integer = null;
int i = integer;
System.out.println(i);

(7)定义合理的默认值

定义成员变量时提供合理的默认值。

代码语言:javascript
复制
public class Main {
    private List<String> list = new ArrayList<>();
    private String s = "";
}

(8)使用空对象模式

空对象是设计的一种特殊实例,为方法提供默认的行为,例如 Collections中的 EMPTY_List,我们仍能使用它的 size(),会返回 0,而不会抛出 NPE。

再举个 Jackson 中的例子,当子节点不存在时, path()会返回一个 MissingNode 对象,当调用 MissingNode 对象的 path() 方法是将继续返回 MissingNode。这样的链式调用将不会抛出 NPE。最后返回后,用户只需检查结果是否为 MissingNode 就能判断是不是找到了。

代码语言:javascript
复制
JsonNode child = root.path("a").path("b");
if (child.isMissingNode()) {
    //...
}

(9)Optional

Optional 是 Java8 的一个新特性,可以为 null 的容器对象。若值存在,不为 null,则 isPresent()方法会返回 true,调用 get()方法可返回该对象。它所起到的作用是避免我们显示的进行空值校验。

举一个常见的空值校验示例:

代码语言:javascript
复制
// 最外层
public class Outer {
    Nested nested;
    Nested getNested() {
        return nested;
    }
}
代码语言:javascript
复制
// 第二层
public class Nested {
    Inner inner;
    Inner getInner() {
        return inner;
    }
}
代码语言:javascript
复制
// 最底层
public class Inner {
    String foo;
    String getFoo() {
        return foo;
    }
}

我们通过 Outer 对象访问 Inner 中的 foo 属性,若加空值校验的话,代码如下:

代码语言:javascript
复制
Outer outer = new Outer();
if (outer != null) {
    if (outer.nested != null) {
        if (outer.nested.inner != null) {
            System.out.println(outer.nested.inner.foo);
        }
    }
}

这种嵌套式的判断语句在空值校验中很常见。而使用 Optional 再结合 Java8 的特性 Lambda 表达式、流处理,可以采用链式操作,更为简洁。

代码语言:javascript
复制
Optional.of(new Outer())
    .map(Outer::getNested)
    .map(Nested::getInner)
    .map(Inner::getFoo)
    .ifPresent(System.out::println);

Optional.of() 方法可以返回一个 Optional<Outer> 的对象,并将 Outer 对象放在容器内, Optinal.map()方法中,会通过 isPresent() 方法判断是否为 null,如果为 null,将返回 Optional<Outer> 类型的空对象,不影响后续的链式调用。是不是很眼熟,这和我们在第 8 点说的空对象模式类似,在 Optional 的实现中也采用了这种模式。

(10)细心

嘿嘿,凑个第十点吧。

最后祝大家成功避开 NullPointerException,有什么其他的好建议,欢迎留言交流!

4. 参考

  1. Java Tips and Best practices to avoid NullPointerException in Java Applications
  2. 如何在 Java8 中风骚走位避开空指针异常
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-02-22,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 草捏子 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. 引言
  • 2. 发生 NPE 的可能情况
  • 3. 避开 NPE 的建议
  • 4. 参考
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档