JDK1.8 新特性 :还在重复写空指针检查代码?赶紧使用 Optional 吧

image.png

1、前言

作为一名Java程序员,无论是初入茅庐的菜鸟,还是久经江湖的高手,曾经肯定遭遇过各种各样的异常错误。在国外的一篇文章中,就统计了关于异常类型的排行榜,如下图:

image

是的,你没有看错,NullPointerException位居榜首。

Null Reference的发明者Charles Antony Richard Hoare说过:“我称之为我的十亿美元错误。这是1965年发明空引用的结果……这导致了无数的错误,漏洞和系统崩溃,在最近40年中可能造成十亿美元的痛苦和破坏。”

这看起来有些夸张,但毫无争议的是NullPointerException简直就是程序员心中的痛,并不是说它有多难以解决,而是为了解决它我们需要再付出了额外代价。

还记得当初刚入行时候的你,三天两头碰到NullPointerException而引发的bug,解决完一个,又在另一个地方遇到。这也慢慢让你懂得,不要相信任何“对象”,特别是别人提供给你的,在使用的地方都加上判断,这样就放心多了。于是代码通常就变成了下面这样:

pre lang="text" data-origin="pm_code_preview" style="margin: 0px; padding: 0px; list-style: none; font-family: Courier, "Courier New", monospace; display: block;">

String name = "Unknown";

if (null != people) {

 if (null != people.getName()) {

 name = people.getName();

 }

}

return name;

这样处理,虽然不用担心NullPointerException了,但是过多的判断语句着实让人头皮发麻,代码变得臃肿不堪。如果对象过于复杂,对象里面还有对象等等,你还要继续逐层判断么?

令人兴奋的是,JDK1.8引入了一个新类java.util.Optional<T>,凭借Optional类提供的API,我们再也不用担心NullPointerException了,更不会再去写那些烦人的判断啦。

2、Optional类

举例来说,使用新类意味着,如果你知道一个人可能有也可能没有车,那么Person类内部的car变量就不应该声明为Car,遭遇某人没有车时把null引用赋值给它,而是应该像下图这样直接将其声明为Optional类型。

image

变量存在时,Optional类只是对类简单封装。变量不存在时,缺失的值会被建模成一个“空”的Optional对象,由方法Optional.empty()返回。Optional.empty()方法是一个静态工厂方法,它返回Optional类的特定单一实例。

Optional,本质上是一个容器对象,拥有一个非空值或空值,需要我们将对象实例传入该容器中。如果值存在,Optional.isPresent()方法返回true,并通过Optional.get()方法获取值。

Optional的构造方法为private,无法直接使用new来创建Optional对象,只能使用Optional提供的静态方法创建。

Optional提供的创建方法如下:

  • Optional.of(obj):如果对象为 null,将会抛出NullPointerException

  • Optional.ofNullable(obj):如果对象为 null,将会创建不包含值的 EMPTY Optional对象实例(new Optional<>())。

  • Optional.empty() :等同于 Optional.ofNullable(null)

其中,源码片段如下:

<pre lang="text" data-origin="pm_code_preview" style="margin: 0px; padding: 0px; list-style: none; font-family: Courier, &quot;Courier New&quot;, monospace; display: block;">

/**

 * Constructs an instance with the value present.

 *

 * @param value the non-null value to be present

 * @throws NullPointerException if value is null

 */

private Optional(T value) {

 this.value = Objects.requireNonNull(value);

}

……

/**

 * Returns an {@code Optional} with the specified present non-null value.

 *

 * @param <T> the class of the value

 * @param value the value to be present, which must be non-null

 * @return an {@code Optional} with the value present

 * @throws NullPointerException if value is null

 */

public static <T> Optional<T> of(T value) {

 return new Optional<>(value);

}
<pre lang="text" data-origin="pm_code_preview" style="margin: 0px; padding: 0px; list-style: none; font-family: Courier, &quot;Courier New&quot;, monospace; display: block;">

/**

 * Returns an {@code Optional} describing the specified value, if non-null,

 * otherwise returns an empty {@code Optional}.

 *

 * @param <T> the class of the value

 * @param value the possibly-null value to describe

 * @return an {@code Optional} with a present value if the specified value

 * is non-null, otherwise an empty {@code Optional}

 */

public static <T> Optional<T> ofNullable(T value) {

 return value == null ? empty() : of(value);

}
<pre lang="text" data-origin="pm_code_preview" style="margin: 0px; padding: 0px; list-style: none; font-family: Courier, &quot;Courier New&quot;, monospace; display: block;">

/**

 * Common instance for {@code empty()}.

 */

private static final Optional<?> EMPTY = new Optional<>();

……

/**

 * Returns an empty {@code Optional} instance.  No value is present for this

 * Optional.

 *

 * @apiNote Though it may be tempting to do so, avoid testing if an object

 * is empty by comparing with {@code ==} against instances returned by

 * {@code Option.empty()}. There is no guarantee that it is a singleton.

 * Instead, use {@link #isPresent()}.

 *

 * @param <T> Type of the non-existent value

 * @return an empty {@code Optional}

 */

public static<T> Optional<T> empty() {

 @SuppressWarnings("unchecked")

 Optional<T> t = (Optional<T>) EMPTY;

 return t;

}

强烈建议使用Optional.ofNullable(obj)方法,来创建Optional对象,并获取对应值。

3、Optional的使用

到目前为止,你已经知道Optional的好处了吧,但是,我们该如何使用呢?

使用可接受null的Optional对象,即:使用静态工程方法Optional.ofNullable(obj),创建一个可以允许null值的Optional对象:

<pre lang="text" data-origin="pm_code_preview" style="margin: 0px; padding: 0px; list-style: none; font-family: Courier, &quot;Courier New&quot;, monospace; display: block;">

Optional<People> optional = Optional.ofNullable(people);

即使people是null,optional对象也就是个空对象。

如果people不为null,根据Optional.isPresent()方法返回true,并通过Optional.get()方法获取值。

为了避免NPE,Optional.isPresent()方法已经对null进行了判断,若存在返回true。

<pre lang="text" data-origin="pm_code_preview" style="margin: 0px; padding: 0px; list-style: none; font-family: Courier, &quot;Courier New&quot;, monospace; display: block;">

People p = null;

if (optional.isPresent()) {

 p = optional.get();

}

看到这里,你可能会发现这与null判断检查并无差异。

后来接触到Optional其他API,我才发现真正体现它价值的是下面这些API。

3.1 **Optional.map**

从对象中获取某个属性,是最常见的操作。比如,你可能需要从people对象中获取人名。在获取人名之前,你需要检查people对象是否为null,如下所示:

<pre lang="text" data-origin="pm_code_preview" style="margin: 0px; padding: 0px; list-style: none; font-family: Courier, &quot;Courier New&quot;, monospace; display: block;">

String name = null;

if (null != people) {

 name = people.getName();

}

使用Optional.map方法,可以这么写:

<pre lang="text" data-origin="pm_code_preview" style="margin: 0px; padding: 0px; list-style: none; font-family: Courier, &quot;Courier New&quot;, monospace; display: block;">

Optional<People> optional = Optional.ofNullable(people);

Optional<String> stringOptional = optional.map(People::getName);

3.2 **Optional.orElse**

当一个对象为 null 时,业务上通常可以设置一个默认值,从而使流程继续下去。

pre lang="text" data-origin="pm_code_preview" style="margin: 0px; padding: 0px; list-style: none; font-family: Courier, &quot;Courier New&quot;, monospace; display: block;">

String name = null != people ? people.getName() : "Unknown";

或者抛出一个异常。

<pre lang="text" data-origin="pm_code_preview" style="margin: 0px; padding: 0px; list-style: none; font-family: Courier, &quot;Courier New&quot;, monospace; display: block;">

if (null != people.getName()) {

 throw new RuntimeException();

}

Optional 类提供两个方法 orElseorElseThrow ,可以方便完成上面转化。

pre lang="text" data-origin="pm_code_preview" style="margin: 0px; padding: 0px; list-style: none; font-family: Courier, &quot;Courier New&quot;, monospace; display: block;">

// 设置默认值

String name = optional.orElse(new People("Unknown")).getName();

// 抛出异常

String name = optional.orElseThrow(RuntimeException::new).getName();

如果 optional 为空,提供默认值或抛出异常。

3.3 **Optional.filter**

你经常需要调用某个对象的方法,查看它的某些属性。比如,你可能需要检查人名是否为“xcbeyond”。为了以一种安全的方式进行这些操作,你首先需要判断people对象是否为null,再调用它的方法getName,如下所示:

<pre lang="text" data-origin="pm_code_preview" style="margin: 0px; padding: 0px; list-style: none; font-family: Courier, &quot;Courier New&quot;, monospace; display: block;">

if (null != people && "xcbeyond".equals(people.getName())) {

 System.out.println("ok");

}

使用Optional类提供的方法filter,可以很好的重构:

pre lang="text" data-origin="pm_code_preview" style="margin: 0px; padding: 0px; list-style: none; font-family: Courier, &quot;Courier New&quot;, monospace; display: block;">

optional.filter(people1 -> "xcbeyond".equals(people.getName()))

 .ifPresent(x -> System.out.print("ok"));

4、Optional重构代码

让我们一起再看看文章开头的代码:

<pre lang="text" data-origin="pm_code_preview" style="margin: 0px; padding: 0px; list-style: none; font-family: Courier, &quot;Courier New&quot;, monospace; display: block;">

String name = "Unknown";

if (null != people) {

 if (null != people.getName()) {

 name = people.getName();

 }

}

return name;

在知道了Optional之后,进行代码重构:

pre lang="text" data-origin="pm_code_preview" style="margin: 0px; padding: 0px; list-style: none; font-family: Courier, &quot;Courier New&quot;, monospace; display: block;">

Optional<People> optional = Optional.ofNullable(people);

return optional.map(People::getName).orElse("Unknown");

结合Optional、Lambda表达式,可以明显看到重构之后,使得代码更加流畅连贯,并且提高代码整体可读性。

猜你喜欢

转载自blog.csdn.net/weixin_47067712/article/details/108150075