从 Java 8 引入的一个很有趣的特性是 Optional 类。Optional 类主要解决的问题是臭名昭著的空指针异常(NullPointerException) —— 每个 Java 程序员都非常了解的异常。

本质上,这是一个包含有可选值的包装类,这意味着 Optional 类既可以含有对象也可以为空。
Optional 是 Java 实现函数式编程的强劲一步,并且帮助在范式中实现。但是 Optional 的意义显然不止于此。

我们从一个简单的用例开始。在 Java 8 之前,任何访问对象方法或属性的调用都可能导致 NullPointerException

  1. String isocode = user.getAddress().getCountry().getIsocode().toUpperCase();

在这个小示例中,如果我们需要确保不触发异常,就得在访问每一个值之前对其进行明确地检查:

  1. if (user != null) {
  2. Address address = user.getAddress();
  3. if (address != null) {
  4. Country country = address.getCountry();
  5. if (country != null) {
  6. String isocode = country.getIsocode();
  7. if (isocode != null) {
  8. isocode = isocode.toUpperCase();
  9. }
  10. }
  11. }
  12. }

你看到了,这很容易就变得冗长,难以维护。
为了简化这个过程,我们来看看用 Optional 类是怎么做的。从创建和验证实例,到使用其不同的方法,
并与其它返回相同类型的方法相结合,下面是见证 Optional 奇迹的时刻。

创建 Optional 实例

重申一下,这个类型的对象可能包含值,也可能为空。你可以使用同名方法创建一个空的 Optional。

  1. @Test(expected = NoSuchElementException.class)
  2. public void whenCreateEmptyOptional_thenNull() {
  3. Optional<User> emptyOpt = Optional.empty();
  4. emptyOpt.get();
  5. }

毫不奇怪,尝试访问 emptyOpt 变量的值会导致 NoSuchElementException
你可以使用 of() 和 ofNullable() 方法创建包含值的 Optional
两个方法的不同之处在于如果你把 null 值作为参数传递进去,of() 方法会抛出 NullPointerException

  1. @Test(expected = NullPointerException.class)
  2. public void whenCreateOfEmptyOptional_thenNullPointerException() {
  3. Optional<User> opt = Optional.of(user);
  4. }

你看,我们并没有完全摆脱 NullPointerException。因此,你应该明确对象不为 null 的时候使用 of()
如果对象即可能是 null 也可能是非 null,你就应该使用 ofNullable() 方法:

  1. Optional<User> opt = Optional.ofNullable(user);

访问 Optional 对象的值
Optional 实例中取回实际值对象的方法之一是使用 get() 方法:

  1. @Test
  2. public void whenCreateOfNullableOptional_thenOk() {
  3. String name = "John";
  4. Optional<String> opt = Optional.ofNullable(name);
  5. assertEquals("John", opt.get());
  6. }

不过,你看到了,这个方法会在值为 null 的时候抛出异常。要避免异常,你可以选择首先验证是否有值:

  1. @Test
  2. public void whenCheckIfPresent_thenOk() {
  3. User user = new User("john@gmail.com", "1234");
  4. Optional<User> opt = Optional.ofNullable(user);
  5. assertTrue(opt.isPresent());
  6. assertEquals(user.getEmail(), opt.get().getEmail());
  7. }

检查是否有值的另一个选择是 ifPresent() 方法。该方法除了执行检查,还接受一个Consumer(消费者) 参数,
如果对象不是空的,就对执行传入的 Lambda 表达式:

  1. opt.ifPresent( u -> assertEquals(user.getEmail(), u.getEmail()));

这个例子中,只有 user 用户不为 null 的时候才会执行断言。
接下来,我们来看看提供空值的方法。

返回默认值

Optional 类提供了 API 用以返回对象值,或者在对象为空的时候返回默认值。
这里你可以使用的第一个方法是 orElse(),它的工作方式非常直接,如果有值则返回该值,否则返回传递给它的参数值:

  1. @Test
  2. public void whenEmptyValue_thenReturnDefault() {
  3. User user = null;
  4. User user2 = new User("anna@gmail.com", "1234");
  5. User result = Optional.ofNullable(user).orElse(user2);
  6. assertEquals(user2.getEmail(), result.getEmail());
  7. }

这里 user 对象是空的,所以返回了作为默认值的 user2
如果对象的初始值不是 null,那么默认值会被忽略:

  1. @Test
  2. public void whenValueNotNull_thenIgnoreDefault() {
  3. User user = new User("john@gmail.com","1234");
  4. User user2 = new User("anna@gmail.com", "1234");
  5. User result = Optional.ofNullable(user).orElse(user2);
  6. assertEquals("john@gmail.com", result.getEmail());
  7. }

第二个同类型的 API 是 orElseGet() —— 其行为略有不同。
这个方法会在有值的时候返回值,如果没有值,它会执行作为参数传入的 Supplier(供应者) 函数式接口,
并将返回其执行结果:

  1. User result = Optional.ofNullable(user).orElseGet( () -> user2);

orElse()orElseGet() 的不同之处
乍一看,这两种方法似乎起着同样的作用。然而事实并非如此。我们创建一些示例来突出二者行为上的异同。
我们先来看看对象为空时他们的行为:

  1. @Test
  2. public void givenEmptyValue_whenCompare_thenOk() {
  3. User user = null
  4. logger.debug("Using orElse");
  5. User result = Optional.ofNullable(user).orElse(createNewUser());
  6. logger.debug("Using orElseGet");
  7. User result2 = Optional.ofNullable(user).orElseGet(() -> createNewUser());
  8. }
  9. private User createNewUser() {
  10. logger.debug("Creating New User");
  11. return new User("extra@gmail.com", "1234");
  12. }

上面的代码中,两种方法都调用了 createNewUser() 方法,这个方法会记录一个消息并返回 User 对象。
代码输出如下:

  1. Using orElse
  2. Creating New User
  3. Using orElseGet
  4. Creating New User

由此可见,当对象为空而返回默认对象时,行为并无差异。

我们接下来看一个类似的示例,但这里 Optional 不为空:

  1. @Test
  2. public void givenPresentValue_whenCompare_thenOk() {
  3. User user = new User("john@gmail.com", "1234");
  4. logger.info("Using orElse");
  5. User result = Optional.ofNullable(user).orElse(createNewUser());
  6. logger.info("Using orElseGet");
  7. User result2 = Optional.ofNullable(user).orElseGet(() -> createNewUser());
  8. }

这次的输出:

  1. Using orElse
  2. Creating New User
  3. Using orElseGet

这个示例中,两个 Optional 对象都包含非空值,两个方法都会返回对应的非空值。
不过,orElse() 方法仍然创建了 User 对象。与之相反,orElseGet() 方法不创建 User 对象。
在执行较密集的调用时,比如调用 Web 服务或数据查询,这个差异会对性能产生重大影响。

返回异常

除了 orElse()orElseGet() 方法,Optional 还定义了 orElseThrow() API —— 它会在对象为空的时候抛出异常,
而不是返回备选的值:

  1. @Test(expected = IllegalArgumentException.class)
  2. public void whenThrowException_thenOk() {
  3. User result = Optional.ofNullable(user)
  4. .orElseThrow( () -> new IllegalArgumentException());
  5. }

这里,如果 user 值为 null,会抛出 IllegalArgumentException
这个方法让我们有更丰富的语义,可以决定抛出什么样的异常,而不总是抛出 NullPointerException
现在我们已经很好地理解了如何使用 Optional,我们来看看其它可以对 Optional 值进行转换和过滤的方法。


转换值

有很多种方法可以转换 Optional 的值。我们从 map()flatMap() 方法开始。
先来看一个使用 map() API 的例子:

  1. @Test
  2. public void whenMap_thenOk() {
  3. User user = new User("anna@gmail.com", "1234");
  4. String email = Optional.ofNullable(user)
  5. .map(u -> u.getEmail()).orElse("default@gmail.com");
  6. assertEquals(email, user.getEmail());
  7. }

map() 对值应用(调用)作为参数的函数,然后将返回的值包装在 Optional 中。这就使对返回值进行链试调用的操作成为可能 —— 这里的下一环就是 orElse()

相比这下,flatMap() 也需要函数作为参数,并对值调用这个函数,然后直接返回结果。
下面的操作中,我们给 User 类添加了一个方法,用来返回 Optional

public class User {    
    private String position;

    public Optional<String> getPosition() {
        return Optional.ofNullable(position);
    }

    //...
}

既然 getter 方法返回 String 值的 Optional,你可以在对 User 的 Optional 对象调用 flatMap() 时,用它作为参数。其返回的值是解除包装的 String 值:

@Test
public void whenFlatMap_thenOk() {
    User user = new User("anna@gmail.com", "1234");
    user.setPosition("Developer");
    String position = Optional.ofNullable(user)
        .flatMap(u -> u.getPosition()).orElse("default");

    assertEquals(position, user.getPosition().get());
}

过滤值
除了转换值之外,Optional 类也提供了按条件“过滤”值的方法。
filter() 接受一个 Predicate 参数,返回测试结果为 true 的值。如果测试结果为 false,会返回一个空的 Optional
来看一个根据基本的电子邮箱验证来决定接受或拒绝 User(用户) 的示例:

@Test
public void whenFilter_thenOk() {
    User user = new User("anna@gmail.com", "1234");
    Optional<User> result = Optional.ofNullable(user)
        .filter(u -> u.getEmail() != null && u.getEmail().contains("@"));

    assertTrue(result.isPresent());
}

如果通过过滤器测试,result 对象会包含非空值。


Optional 类的链式方法

为了更充分的使用 Optional,你可以链接组合其大部分方法,因为它们都返回相同类似的对象。
我们使用 Optional 重写最早介绍的示例。
首先,重构类,使其 getter 方法返回 Optional 引用:

public class User {
    private Address address;

    public Optional<Address> getAddress() {
        return Optional.ofNullable(address);
    }

    // ...
}
public class Address {
    private Country country;

    public Optional<Country> getCountry() {
        return Optional.ofNullable(country);
    }

    // ...
}

上面的嵌套结构可以用下面的图来表示:
Optional类 - 图1
现在可以删除 null 检查,替换为 Optional 的方法:

@Test
public void whenChaining_thenOk() {
    User user = new User("anna@gmail.com", "1234");

    String result = Optional.ofNullable(user)
        .flatMap(u -> u.getAddress())
        .flatMap(a -> a.getCountry())
        .map(c -> c.getIsocode())
        .orElse("default");

    assertEquals(result, "default");
}

上面的代码可以通过方法引用进一步缩减:

String result = Optional.ofNullable(user)
    .flatMap(User::getAddress)
    .flatMap(Address::getCountry)
    .map(Country::getIsocode)
    .orElse("default");
结果现在的代码看起来比之前采用条件分支的冗长代码简洁多了。