Java 8 | Optional

我想在搬砖的过程中,大家一定都遇到过 NPE (NullPointerException) 吧。当我们尝试使用一个还没有初始化或者初始化为 null 的变量时,就会出现 NPE。Java 8 添加了 Optional 来尝试解决这个问题。接下来让我们来了解一下这个类吧。

空(Null) 到底是什么类型 ?

在 Java 中,我们使用引用来访问一个对象实例,当这个引用不知道指向那个对象实例时,我们通常都将它设置为 null。

平时在搬砖过程中,null 的使用是那么的普遍,以致于我们很少会去考虑到它。如果没有特殊指定,对象的实例变量通常都自动初始化为 null,当我们不知道给这个引用赋什么值的时候,通常也会将 null 赋值给它。

那么 空(Null) 到底是什么类型呢?

仅供参考,在 Java 中,空(Null) 其实也是一种类型,只不过它很特殊,我们只能将 null 这个字面量赋值给这种类型。而且这种类型不像其他的 Java 类型,null 可以赋值给任何其它的 Java 类型而不会出现错误。

返回 null 有什么问题吗 ?

在开发过程中,我们经常会使用第三方提供的 API 接口,但是这些 API 接口的返回值可能为 null,返回结果的说明可能在 API 文档中已经有说明,但是当我们开发的时候,由于各种原因,没有仔细阅读文档(甚至就没有读过文档),忘记了处理返回值为 null 的情况,那么这就可能会存在 NPE 问题。而且这种现象在平时开发过程中是经常发生的。

那么怎么有效的解决这个问题呢?一个比较好的办法就是对返回值都进行初始化,但是初始化的值不能是 null(例子就是,如果方法返回值是一个 List,那么我们可以返回一个 Collections.emptyList()),这样就不会出现 NPE 了,你们说是不是这个道理?

嗯,理想很美好,现实很骨感。在实际中,我们有可能找不到这么一个不是 null 的初始化值,那么该怎么办呢?于是 Optional 出现了。

Java 8 的 Optional 怎么解决 NPE 呢?

Optional 就是一个引用为空的非空值。Optional 可能包含一个非空的引用,也可能不会包含什么,记住永远不要说 Optional 包含 null。

Optional canBeEmpty1 = Optional.of(5);
canBeEmpty1.isPresent();                    // returns true
canBeEmpty1.get();                          // returns 5

Optional canBeEmpty2 = Optional.empty();
canBeEmpty2.isPresent();                    // returns false

我们可以把 Optional 当做一个单值容器,它里面可能会装一个值,也可能什么都不装。

需要说明的是 Optional 类并不是替代 null 的,加入 Optional 类是为了可以设计出更容易理解的 API 接口。比如

public User getUserFromDB(long id);

我们可能调用这个接口后,直接就把 User 这个引用拿来使用了,忘记判断引用可能为空的情况。那么如果这个接口是这样的

public Optional getUserFromDB(long id);

当我们看到这个就接口的时候就会意识到,这个接口返回的结果可能会为空,这样我们就会先对这个值做判断,然后再使用它。是不是一眼就明白了。

下面我们举一些使用 Optional 的例子

a) 创建 Optional 对象

有 3 种方法来创建 Optional 对象。

i) 使用 Optional.empty() 创建没有值的 Optional 容器。

Optional possible = Optional.empty();

ii) 使用 Optional.of() 创建一个装有值的 Optional 容器。如果你传一个 null 给 Optional.of() 方法,那么会抛出 NullPointerException。

Optional possible = Optional.of(5);

iii) 使用 Optional.ofNullable() 创建一个可能没有值的 Optional 容器。

Optional possible = Optional.ofNullable(null);
//or
Optional possible = Optional.ofNullable(5);

b) Optional 容器有值处理

当获得了 Optional 对象,首先应该检查一下 Optional 容器中是否有值。

Optional possible = Optional.of(5);
possible.ifPresent(System.out::println);

当然,上面这段代码也可以写成下面传统的样子

if(possible.isPresent()){
    System.out.println(possible.get());
}

但是这并不是推荐的写法,要不然 Java 8 的 lambda 表达式有何用?

c) Optional 容器无值处理

当返回值不存在的时候,通常情况下我们会返回一个默认值。一般我们会使用三元运算符来处理,但是使用 Optional 我们可以这样

//Assume this value has returned from a method
Optional companyOptional = Optional.empty();
 
//Now check optional; if value is present then return it,
//else create a new Company object and retur it
Company company = companyOptional.orElse(new Company());
 
//OR you can throw an exception as well
Company company = companyOptional.orElseThrow(IllegalStateException::new);

d) 通过 filter 方法做过滤

通常我们回去一个结果需要对这个结果做一些过滤,筛选出符合我们要求的结果。

Optional companyOptional = Optional.empty();
companyOptional.filter(department -> "Finance".equals(department.getName())
                    .ifPresent(() -> System.out.println("Finance is present"));

filter 方法接受一个 predicate 参数。如果 Optional 有值而且满足 predicate,filter 方法就会返回这个值,否则就会返回一个无值的 Optional。

哇哦,现在我们已经不用在代码中写各式各样的 null 值判断逻辑了,是不是看着更简洁了呢。

Optional 内部是怎么处理的呢?

看看 Optional.java,Optional 持有的值定义

/**
 * If non-null, the value; if null, indicates no value is present
 */
private final T value;

如果值为空,使用的是一个静态变量表示

/**
 * Common instance for {@code empty()}.
 */
private static final Optional EMPTY = new Optional<>();

Optional 类的构造函数都是私有的,这样就确保只能使用上面提供的3种创建 Optional 实例。

当创建一个有值的 Optional 实例时,会对传入的 value 进行判断

this.value = Objects.requireNonNull(value);

当从 Optional 获取值得时候,如果 value 为 null 会抛出 NoSuchElementException 异常

public T get() {
    if (value == null) {
        throw new NoSuchElementException("No value present");
    }
    return value;
}

同样的,Optional 类中的其他方法都是围绕着这个 value 进行的。

Opti0nal 尝试解决的

Optional 类尝试解决 Java 中 NPE 问题。通过 Optional 类设计出更容易理解的 API,如果 Optional 类从开始就被设计出来,我相信在现在的类库、应用中对 null 值的处理会更加合理。

通过使用Optional 类,这回强迫开发者思考为空的异常情况。当编写 API 的时候,如果返回值是Optional 类,编写 API 的开发者不得不考虑为空的这种情况,因为不能直接返回 null 了,要不然编译也不会通过的。

Opti0nal 不能解决的

正如前面说到的,Opti0nal 并不能代替所有 null 的情况。当函数的入参有 null 的时候,并不推荐使用 Opti0nal。你想想当你调用一个接口的时候,我们还得封装一个成 Opti0nal 对象给它,是不是有点麻烦。

为了更优雅的使用 Opti0nal 类,下面情况最好不要使用

  1. 在数据模型层(Opti0nal 不可序列化)
  2. 在 DTO 中(Opti0nal 不可序列化)
  3. 在方法的入参中
  4. 在构造函数中

在什么地方使用 Opti0nal 呢?

Opti0nal 应该作为可能返回为空的方法的返回值类型。

下面这段引用时 OpenJDK 中的部分

The JSR-335 EG felt fairly strongly that Optional should not be on any more than needed to support the optional-return idiom only.
Someone suggested maybe even renaming it to "OptionalReturn".

这段话我觉得表达的就是 Opti0nal 应该作为可能返回为空的方法的返回值类型吧。

总结

本文主要介绍了 java.util.Optional 的使用。 Opti0nal 类的目的并不是取代 null,而是帮助设计出更好理解的 API 接口,通过方法返回值,提醒开发者处理可能出现 null 的异常情况。

你可能感兴趣的:(Java 8 | Optional)