原文链接:https://nipafx.dev/intention-revealing-code-java-8-optional/

使用 Optional 编写意图清晰的代码,并且避免大多数的 NPEs,这是非常有必要的。

Java 8 引入了一个类 Optional ,用来处理可能不存在的值。它包装了一个可能为空的对象,并且提供了一些很好用的方法操作包含的对象。

大多数人对于 Optional 的看法都包含在这里,或者这里以及这里。在我看来,他们忽略了了最关键的一点:Optional 提供了一个消除 null 的方式。

尽管还存在注意事项,但是使用 Optional 去做一些牺牲是值得的。尽管在某些情况下, 有比 Optional 更好的解决方案,但不在这篇文章的讨论范围。
我们只想关注 Optional 是如何处理 null 的。

Optional 介绍

Optional 是一个设计非常简单的包装类,它在 Javadoc 中的描述如下:

A container object which may or may not contain a non-null value. If a value is present, isPresent() will return true and get() will return the value. Additional methods that depend on the presence or absence of a contained value are provided, such as orElse() (return a default value if value not present) and ifPresent() (execute a block of code if the value is present).

构造函数

有三种方法创建一个 Optional 实例:

  1. // 一个空的 Optional
  2. // 在 Java 8 之前,你可以简单使用 null 来表示
  3. Optional<String> empty = Optional.empty();
  4. // 一个不存在空值的 Optional
  5. // 如果 of 方法的参数为 null,则会抛出 NullPointerException
  6. Optional<String> full = Optional.of("Some String");
  7. // 一个可能存在空值的 Optional
  8. Optional<String> halfFull = Optional.ofNullable(someOtherString);

of方法和 ofNullable 方法似乎有些多余。如果你只是为了得到一个 NullPointerException 为什么还要使用 of方法?往下看就知道了。

常用方法

Optional 最基本的两个方法:

  • boolean isPresent(),当 Optional 内元素值不为空时,返回 true。反之,返回 false。
  • T get()如果值存在,返回该值,否则抛出 NoSuchElementException。

这就意味着,每当使用 get() 方法获取值时,都需要先使用 isPresent()来判断值是否存在。

Optional 提供了许多其他方法,来实现一些骚操作。你可以在 Javadoc 或者在网上的文章中了解这些方法。如果你真的迫切想要了解的话,可以参考这两篇文章 EasyBindwhich makes similar functions available to JavaFX’ ObservableValues

为什么要使用 Optional

Optional 本身并没有什么特别之处,使用之前的 null 也并没有什么问题。Optional 的神奇之处不在于它的静态工厂方法,也不在普通方法和对 lambda 的支持。

Optional 最妙的在于它的名字,简单明了在详细阐述之前,花点时间探讨下 null

null

假设代码有个 NullPointerException,并且无法快速定位修复的。你只好查看代码,一行行的排查,现在尝试回答几个问题:

  • 这个 null 从哪里来的?
  • 它真的是个对象的实例吗?
  • 对于这个变量来说,返回 null 合理吗?
  • 它是其他方法返回的值吗?

这个问题可能会让你花上一个小时的时间,它之所以如此复杂,是因为 null 可能包含其它意思。例如:变量没有初始化、当前为空。在代码中表现都是一样的,只有自己摸索后才知道他们的区别。

因此只有写这段代码的人才能区分此处 null 意图,而维护这段代码的人,则必须在成千行代码中找到引用,明白写这段代码的人的意图。

意图明确之后,你可以自行处理。如果 null 是有效值,表示一个不存在的值或特殊值,你应该先检查 null。或者不存在 null 的情况,你应该在创建它的时候就把问题暴露出来。

要是有个方法能分区这些意图就好了……

表达意图

使用 Optional 可以准确地表达意图。如果你的意图是:变量在某些时间可能为空,可以使用 Optional 作为参数。或者调用函数可能没有返回值,可以使用 Optional 作为返回类型。

如果你的 API 的属性、参数、返回值不是 Optional ,那你必须保证调用者调用该 API 传递的参数不能为空,返回的值也不能为空。

此外,FindBugs 也支持自动检查以下情况:

如果你还有其他关于 FindBugs 检查的想法,在 SourceForge 上开个贴,或者在下方评论。

Fail Fast

使用 Optional 的另一个原因是:让程序 failing fast。Optional 不确定当前值是否存在,在每次调用的时候对 Optional 里的值进行校验。

上面提到了两个静态工厂方法 ofofNullable。当对象一定不为空时,可以使用 of,若果该对象实际为空,of 方法就会得到一个异常。当对象可能为空时,那么应该使用 ofNullable

严格遵守该规范,有助于尽早发现潜在的错误。

结论

不用再猜测 Null 的意图

对我来说,这是使用 Optional 最令人信服的观点。

如果程序最后还是出现 NullPointerException。你还是要同之前一样,找到报空指针的代码段,并且你知道此处不能出现 null。因此,你可以在代码中加入更多的 null 检查,直到找到问题所在并修复它。

反过来,程序可能出现 NoSuchElementException (当值不存在时, Optional.get() 会抛出该异常)。这些问题却很容易解决,只需要在取值之前检查当前值是否存在即可。

不用再考虑 Null 的情况

另一个很重要的点:不需要花费太多经精力去考虑 null 的情况。你不用在去看注释,看相关代码。但如果不用 Optional 的话,这些操作都是必要的。

更多关于 null 的检测

使用 Optional 的一个必然结果是,可以更容易地针对 null 进行检测。无论你是使用 assertions还是 Objects.requireNonNull,都不必再问自己,这个参数是否可以被允许为 null,肯定不允许(因为允许的话使用 Optional 来代替)。

发散思维

这篇文章强烈支持你使用 Optional。文章强调了 Optional 的主要特性是它的名字,因为它比直接用 null 表达的意图更明显:该值可能不存在。它展示了没有 null 的代码是什么样子。

对 Optional 你有什么想说的吗?或许你不认同使用 Optional,你可以加入下面的讨论发表你自己的看法。你也可以在 StackOverflow 进行讨论,我从 StackOverflow 挑选了一些回答