Java 空指针异常的若干解决方案

article/2025/9/25 17:41:50

Java 中任何对象都有可能为空,当我们调用空对象的方法时就会抛出 NullPointerException 空指针异常,这是一种非常常见的错误类型。我们可以使用若干种方法来避免产生这类异常,使得我们的代码更为健壮。本文将列举这些解决方案,包括传统的空值检测、编程规范、以及使用现代 Java 语言引入的各类工具来作为辅助。

运行时检测

最显而易见的方法就是使用 if (obj == null) 来对所有需要用到的对象来进行检测,包括函数参数、返回值、以及类实例的成员变量。当你检测到 null 值时,可以选择抛出更具针对性的异常类型,如 IllegalArgumentException,并添加消息内容。我们可以使用一些库函数来简化代码,如 Java 7 开始提供的 Objects#requireNonNull 方法:

public void testObjects(Object arg) {Object checked = Objects.requireNonNull(arg, "arg must not be null");checked.toString();
}

Guava 的 Preconditions 类中也提供了一系列用于检测参数合法性的工具函数,其中就包含空值检测:

public void testGuava(Object arg) {Object checked = Preconditions.checkNotNull(arg, "%s must not be null", "arg");checked.toString();
}

我们还可以使用 Lombok 来生成空值检测代码,并抛出带有提示信息的空指针异常:

public void testLombok(@NonNull Object arg) {arg.toString();
}

生成的代码如下:

public void testLombokGenerated(Object arg) {if (arg == null) {throw new NullPointerException("arg is marked @NonNull but is null");}arg.toString();
}

这个注解还可以用在类实例的成员变量上,所有的赋值操作会自动进行空值检测。

编程规范

通过遵守某些编程规范,也可以从一定程度上减少空指针异常的发生。

  • 使用那些已经对 null 值做过判断的方法,如 String#equalsString#valueOf、以及三方库中用来判断字符串和集合是否为空的函数:
if (str != null && str.equals("text")) {}
if ("text".equals(str)) {}if (obj != null) { obj.toString(); }
String.valueOf(obj); // "null"// from spring-core
StringUtils.isEmpty(str);
CollectionUtils.isEmpty(col);
// from guava
Strings.isNullOrEmpty(str);
// from commons-collections4
CollectionUtils.isEmpty(col);
  • 如果函数的某个参数可以接收 null 值,考虑改写成两个函数,使用不同的函数签名,这样就可以强制要求每个参数都不为空了:
public void methodA(Object arg1) {methodB(arg1, new Object[0]);
}public void methodB(Object arg1, Object[] arg2) {for (Object obj : arg2) {} // no null check
}
  • 如果函数的返回值是集合类型,当结果为空时,不要返回 null 值,而是返回一个空的集合;如果返回值类型是对象,则可以选择抛出异常。Spring JdbcTemplate 正是使用了这种处理方式:
// 当查询结果为空时,返回 new ArrayList<>()
jdbcTemplate.queryForList("SELECT * FROM person");// 若找不到该条记录,则抛出 EmptyResultDataAccessException
jdbcTemplate.queryForObject("SELECT age FROM person WHERE id = 1", Integer.class);// 支持泛型集合
public <T> List<T> testReturnCollection() {return Collections.emptyList();
}

静态代码分析

Java 语言有许多静态代码分析工具,如 Eclipse IDE、SpotBugs、Checker Framework 等,它们可以帮助程序员检测出编译期的错误。结合 @Nullable@Nonnull 等注解,我们就可以在程序运行之前发现可能抛出空指针异常的代码。

但是,空值检测注解还没有得到标准化。虽然 2006 年 9 月社区提出了 JSR 305 规范,但它长期处于搁置状态。很多第三方库提供了类似的注解,且得到了不同工具的支持,其中使用较多的有:

  • javax.annotation.Nonnull:由 JSR 305 提出,其参考实现为 com.google.code.findbugs.jsr305
  • org.eclipse.jdt.annotation.NonNull:Eclipse IDE 原生支持的空值检测注解;
  • edu.umd.cs.findbugs.annotations.NonNull:SpotBugs 使用的注解,基于 findbugs.jsr305
  • org.springframework.lang.NonNull:Spring Framework 5.0 开始提供;
  • org.checkerframework.checker.nullness.qual.NonNull:Checker Framework 使用;
  • android.support.annotation.NonNull:集成在安卓开发工具中;

我建议使用一种跨 IDE 的解决方案,如 SpotBugs 或 Checker Framework,它们都能和 Maven 结合得很好。

SpotBugs 与 @NonNull@CheckForNull

SpotBugs 是 FindBugs 的后继者。通过在方法的参数和返回值上添加 @NonNull@CheckForNull 注解,SpotBugs 可以帮助我们进行编译期的空值检测。需要注意的是,SpotBugs 不支持 @Nullable 注解,必须用 @CheckForNull 代替。如官方文档中所说,仅当需要覆盖 @ParametersAreNonnullByDefault 时才会用到 @Nullable

官方文档 中说明了如何将 SpotBugs 应用到 Maven 和 Eclipse 中去。我们还需要将 spotbugs-annotations 加入到项目依赖中,以便使用对应的注解。

<dependency><groupId>com.github.spotbugs</groupId><artifactId>spotbugs-annotations</artifactId><version>3.1.7</version>
</dependency>

以下是对不同使用场景的说明:

@NonNull
private Object returnNonNull() {// 错误:returnNonNull() 可能返回空值,但其已声明为 @Nonnullreturn null;
}@CheckForNull
private Object returnNullable() {return null;
}public void testReturnNullable() {Object obj = returnNullable();// 错误:方法的返回值可能为空System.out.println(obj.toString());
}private void argumentNonNull(@NonNull Object arg) {System.out.println(arg.toString());
}public void testArgumentNonNull() {// 错误:不能将 null 传递给非空参数argumentNonNull(null);
}public void testNullableArgument(@CheckForNull Object arg) {// 错误:参数可能为空System.out.println(arg.toString());
}

对于 Eclipse 用户,还可以使用 IDE 内置的空值检测工具,只需将默认的注解 org.eclipse.jdt.annotation.Nullable 替换为 SpotBugs 的注解即可:

Eclipse null analysis

Checker Framework 与 @NonNull@Nullable

Checker Framework 能够作为 javac 编译器的插件运行,对代码中的数据类型进行检测,预防各类问题。我们可以参照 官方文档,将 Checker Framework 与 maven-compiler-plugin 结合,之后每次执行 mvn compile 时就会进行检查。Checker Framework 的空值检测程序支持几乎所有的注解,包括 JSR 305、Eclipse、甚至 lombok.NonNull

import org.checkerframework.checker.nullness.qual.Nullable;@Nullable
private Object returnNullable() {return null;
}public void testReturnNullable() {Object obj = returnNullable();// 错误:obj 可能为空System.out.println(obj.toString());
}

Checker Framework 默认会将 @NonNull 应用到所有的函数参数和返回值上,因此,即使不添加这个注解,以下程序也是无法编译通过的:

private Object returnNonNull() {// 错误:方法声明为 @NonNull,但返回的是 null。return null;
}private void argumentNonNull(Object arg) {System.out.println(arg.toString());
}public void testArgumentNonNull() {// 错误:参数声明为 @NonNull,但传入的是 null。argumentNonNull(null);
}

Checker Framework 对使用 Spring Framework 5.0 以上的用户非常有用,因为 Spring 提供了内置的空值检测注解,且能够被 Checker Framework 支持。一方面我们无需再引入额外的 Jar 包,更重要的是 Spring Framework 代码本身就使用了这些注解,这样我们在调用它的 API 时就能有效地处理空值了。举例来说,StringUtils 类里可以传入空值的函数、以及会返回空值的函数都添加了 @Nullable 注解,而未添加的方法则继承了整个框架的 @NonNull 注解,因此,下列代码中的空指针异常就可以被 Checker Framework 检测到了:

// 这是 spring-core 中定义的类和方法
public abstract class StringUtils {// str 参数继承了全局的 @NonNull 注解public static String capitalize(String str) {}@Nullablepublic static String getFilename(@Nullable String path) {}
}// 错误:参数声明为 @NonNull,但传入的是 null。
StringUtils.capitalize(null);String filename = StringUtils.getFilename("/path/to/file");
// 错误:filename 可能为空。
System.out.println(filename.length());

Optional 类型

Java 8 引入了 Optional<T> 类型,我们可以用它来对函数的返回值进行包装。这种方式的优点是可以明确定义该方法是有可能返回空值的,因此调用方必须做好相应处理,这样也就不会引发空指针异常。但是,也不可避免地需要编写更多代码,而且会产生很多垃圾对象,增加 GC 的压力,因此在使用时需要酌情考虑。

Optional<String> opt;// 创建
opt = Optional.empty();
opt = Optional.of("text");
opt = Optional.ofNullable(null);// 判断并读取
if (opt.isPresent()) {opt.get();
}// 默认值
opt.orElse("default");
opt.orElseGet(() -> "default");
opt.orElseThrow(() -> new NullPointerException());// 相关操作
opt.ifPresent(value -> {System.out.println(value);
});
opt.filter(value -> value.length() > 5);
opt.map(value -> value.trim());
opt.flatMap(value -> {String trimmed = value.trim();return trimmed.isEmpty() ? Optional.empty() : Optional.of(trimmed);
});

方法的链式调用很容易引发空指针异常,但如果返回值都用 Optional 包装起来,就可以用 flatMap 方法来实现安全的链式调用了:

String zipCode = getUser().flatMap(User::getAddress).flatMap(Address::getZipCode).orElse("");

Java 8 Stream API 同样使用了 Optional 作为返回类型:

stringList.stream().findFirst().orElse("default");
stringList.stream().max(Comparator.naturalOrder()).ifPresent(System.out::println);

此外,Java 8 还针对基础类型提供了单独的 Optional 类,如 OptionalIntOptionalDouble 等,在性能要求比较高的场景下很适用。

其它 JVM 语言中的空指针异常

Scala 语言中的 Option 类可以对标 Java 8 的 Optional。它有两个子类型,Some 表示有值,None 表示空。

val opt: Option[String] = Some("text")
opt.getOrElse("default")

除了使用 Option#isEmpty 判断,还可以使用 Scala 的模式匹配:

opt match {case Some(text) => println(text)case None => println("default")
}

Scala 的集合处理函数库非常强大,Option 则可直接作为集合进行操作,如 filermap、以及列表解析(for-comprehension):

opt.map(_.trim).filter(_.length > 0).map(_.toUpperCase).getOrElse("DEFAULT")
val upper = for {text <- opttrimmed <- Some(text.trim())upper <- Some(trimmed) if trimmed.length > 0
} yield upper
upper.getOrElse("DEFAULT")

Kotlin 使用了另一种方式,用户在定义变量时就需要明确区分 可空和不可空类型。当可空类型被使用时,就必须进行空值检测。

var a: String = "text"
a = null // 错误:无法将 null 赋值给非空 String 类型。val b: String? = "text"
// 错误:操作可空类型时必须使用安全操作符(?.)或强制忽略(!!.)。
println(b.length)val l: Int? = b?.length // 安全操作
b!!.length // 强制忽略,可能引发空值异常

Kotlin 的特性之一是与 Java 的可互操作性,但 Kotlin 编译器无法知晓 Java 类型是否为空,这就需要在 Java 代码中使用注解了,而 Kotlin 支持的 注解 也非常广泛。Spring Framework 5.0 起原生支持 Kotlin,其空值检测也是通过注解进行的,使得 Kotlin 可以安全地调用 Spring Framework 的所有 API。

结论

在以上这些方案中,我比较推荐使用注解来预防空指针异常,因为这种方式十分有效,对代码的侵入性也较小。所有的公共 API 都应该使用 @Nullable@NonNull 进行注解,这样就能强制调用方对空指针异常进行预防,让我们的程序更为健壮。

参考资料

  • https://howtodoinjava.com/java/exception-handling/how-to-effectively-handle-nullpointerexception-in-java/
  • http://jmri.sourceforge.net/help/en/html/doc/Technical/SpotBugs.shtml
  • https://dzone.com/articles/features-to-avoid-null-reference-exceptions-java-a
  • https://medium.com/@fatihcoskun/kotlin-nullable-types-vs-java-optional-988c50853692

http://chatgpt.dhexx.cn/article/DQvIS9ZJ.shtml

相关文章

NullPointerException(空指针异常)

NullPointerException(空指针异常)对象为Null还拿来使用&#xff0c;就会出现此异常。 多的不说少的不聊&#xff0c;直接上代码&#xff01;&#xff01;&#xff01; public class Main {public static void main(String[] args) {String str1 "";String str2 n…

Java中的空指针异常

Java中的空指针异常 一、什么是空指针异常&#xff1f; 1.1 异常的分类 NullPointerException是RuntimeException的一个子类&#xff0c;这是运行时异常&#xff0c;在编译时期不会触发。 1.2 空指针异常引入 Java是没有指针的&#xff0c;所以我们常说"Java 指针&quo…

PageRank算法介绍

互联网上有数百亿个网页&#xff0c;可以分为这么几类&#xff1a;不含有用信息的&#xff0c;比如垃圾邮件&#xff1b;少数人比较感兴趣的&#xff0c;但范围不是很广的&#xff0c;比如个人博客、婚礼公告或家庭像册&#xff1b;很多人感兴趣的并且十分有用的&#xff0c;比…

PageRank 算法(从原理到实现)

spark 系列 Spark 核心原理及运行架构 Spark RDD详解 Spark 常用算子大全 Spark SQL 详解 Spark GraphX 图计算入门基础 Spark PageRank 算法——从原理到实现 Spark PageRank spark 系列前言算法来源算法原理排名泄露排名下沉排名上升算法证明 PR值计算方法幂迭代法特征…

浅谈PageRank算法

TOC[目录] PageRank 是 由佩奇(Larry Page)等人提出 的 Google 最为有名的技术之一 PageRank 是一种基于随机游走 的 评价网站权值的算法 总之&#xff0c; PageRank 是一种十分重要的算法 不管在学术界 还是在产业界 Node Similarity(节点相似度) 假设在一个图G(V,E)中研究两…

PageRank算法 到 textRank

1. PageRank算法概述 PageRank,即网页排名&#xff0c;又称网页级别、Google左侧排名或佩奇排名。 是Google创始人拉里佩奇和谢尔盖布林于1997年构建早期的搜索系统原型时提出的链接分析算法&#xff0c;自从Google在商业上获得空前的成功后&#xff0c;该 算法也成为其他搜索引…

PageRank算法浅析

转载请注明出处&#xff01;&#xff01;&#xff01;http://blog.csdn.net/zhonghuan1992 本文是根据 Topic-Sensitive PageRank Google’s PageRank:The Math Behind the Search Engine http://blog.csdn.net/hguisu/article/details/7996185 http://blog.codinglabs.…

PageRank 算法详解

转载自&#xff1a;https://blog.csdn.net/m0_37786726/article/details/79864012 参考文献&#xff1a;https://blog.csdn.net/androidlushangderen/article/details/43311943 链接分析 在链接分析中有2个经典的算法&#xff0c;1个是PageRank算法&#xff0c;还有1个是HITS…

数据挖掘十大算法:PageRank算法原理及实现

一、PageRank的概念 PageRank&#xff0c;网页排名&#xff0c; 是一种由根据网页之间相互的超链接计算的技术&#xff0c;而作为网页排名的要素之一&#xff0c; 它由Larry Page 和 Sergey Brin在20世纪90年代后期发明&#xff0c;并以拉里佩吉&#xff08;Larry Page&#xf…

PageRank 算法及实例分析

本文一部分是针对图的PageRank 的实现&#xff0c;以及具体数据集的分析过程的记录。 另一部分是BFS的实现&#xff0c;并记录每一层的节点数。 数据集下载地址 soc-Slashdot0811 、 roadNet-CA 、 soc-LiveJournal1 1. java 实现代码 Main.java import java.util.List;pu…

PageRank算法(二)

原文地址&#xff1a;https://blog.csdn.net/monkey_d_meng/article/details/6556295 说明&#xff1a;这是我学习过程中看到对PageRank来龙去脉解释非常清晰的博客&#xff0c;博主很厉害&#xff0c;大家可以关注一下原创作者&#xff01; 一、PageRank算法的简单举例 Goo…

PageRank 算法实现

大数据管理与分析实验报告 实验一 大数据系统基本实验 实验二 文档倒排索引算法实现 实验三 PageRank 算法实现 实验目的 PageRank 网页排名的算法&#xff0c;曾是Google 发家致富的法宝。用于衡量特定网页相对于搜索引擎索引中的其他网页而言的重要程度。通过对PageRank 的…

(简单介绍)PageRank算法

文章目录 前言引入形式化PageRank 前言 这个是一个经典算法&#xff0c;还是有必要了解的&#xff0c;这里由于讲得不会很详细&#xff0c;所以要求你有一点数学知识&#xff0c;如果有&#xff0c;看完这篇就大概明白PageRank是个啥了。本篇不涉及证明之类的&#xff0c;而是…

算法--PageRank

概念 PageRank是Google提出的算法&#xff0c;用于衡量特定网页相对于搜索引擎索引中的其他网页而言的重要程度。是Google创始人拉里佩奇和谢尔盖布林于1997年创造的PageRank实现了将链接价值概念作为排名因素。 GOOGLE PageRank并不是唯一的链接相关的排名算法&#xff0c;而…

pagerank以及个性化的pagerank算法

pagerank以及个性化的pagerank算法 pagerank最开始是Google提出来用来衡量网页重要度排行的算法。 她的思想是基于网页之间互相的链接作为加权投票。假如网页a指向b&#xff0c; 那么网页b的重要程度受网页a的影响&#xff0c;a越重要&#xff0c;则b就越重要。假如网页c也指…

PageRank算法原理详解

&#xfeff;&#xfeff; 转自&#xff1a;http://blog.csdn.net/hguisu/article/details/7996185 1. PageRank算法概述 PageRank,即网页排名&#xff0c;又称网页级别、Google左侧排名或佩奇排名。 是Google创始人拉里佩奇和谢尔盖布林于1997年构建早期的搜索系统原型时提出…

PageRank算法改进

PageRank算法的应用 PageRank 算法是 Google 搜索引擎进行网页排名的一种算法&#xff0c;那么它如何映射到其他领域&#xff1f; 比如&#xff0c;我们如何在文献排名中应用PageRank算法呢&#xff1f; 对文献的质量进行排序是对文献价值进行评估的一种重要手段&#xff0c…

什么是Pagerank?Pagerank算法介绍与计算公式

一、什么是Pagerank&#xff1f; PageRank&#xff0c;网页排名&#xff0c;又称网页级别、Google左侧排名或佩奇排名&#xff0c;是一种由根据网页之间相互的超链接计算的技术&#xff0c;而作为网页排名的要素之一&#xff0c;而我们SEO简称为PR&#xff0c;以Google公司创办…

PageRank算法 -- 从原理到实现

本文整理自博文PageRank算法 – 从原理到实现 1. 算法来源 这个要从搜索引擎的发展讲起。最早的搜索引擎采用的是 分类目录1的方法,即通过人工进行网页分类并整理出高质量的网站。那时 Yahoo 和国内的 hao123 就是使用的这种方法。 后来网页越来越多,人工分类已经不现实了…

第4关: 网页排序——PageRank算法

要求&#xff1a;编写实现网页数据集PageRank算法的程序&#xff0c;对网页数据集进行处理得到网页权重排序。 ####相关知识 ######PageRank算法原理 1.基本思想&#xff1a; 如果网页T存在一个指向网页A的连接&#xff0c;则表明T的所有者认为A比较重要&#xff0c;从而把T的一…