6.4 Optional.flatMapOptional.map方法

问题

用户希望避免将一个 Optional 包装在另一个 Optional 中。

方案

使用 Optional 类定义的 flatMap 方法。

讨论

有关 Stream 接口定义的 mapflatMap 方法请参见范例 3.11。不过 flatMap 是一个通用的概念,同样可以用于 Optional

Optional.flatMap 方法的签名如下:

  1. <U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper)

Optional.flatMap 方法的签名与 Optional.map 方法类似,二者的 Function 参数应用于每个元素并生成一个结果,返回类型为 Optional。具体而言,如果参数 T 存在,Optional.flatMap 方法将函数应用于 T 并返回 Optional,它包装包含的值;如果 T 不存在,方法将返回一个空 Optional

根据范例 6.3 的讨论,数据访问对象(DAO)通常采用返回 Optional 的 getter 方法编写(如果属性可以为 null),但 setter 方法不会将参数包装在 Optional 中。例 6-13 显示了两个类,一个是 Manager 类,它包含非空字符串 name;另一个是 Department 类,它包含可空 Manager 特性 boss

例 6-13 包含 Optional 的 DAO 层(部分)

  1. public class Manager {
  2. private String name;
  3.  
  4. public Manager(String name) {
  5. this.name = name;
  6. }
  7.  
  8. public String getName() {
  9. return name;
  10. }
  11. }
  12.  
  13. public class Department {
  14. private Manager boss;
  15.  
  16. public Optional<Manager> getBoss() {
  17. return Optional.ofNullable(boss);
  18. }
  19.  
  20. public void setBoss(Manager boss) {
  21. this.boss = boss;
  22. }
  23. }

❶ 假定不为 null,因此不需要 Optional

❷ 可能为 null,因此在 Optional 中包装 getter 方法并返回,但不要对 setter 方法执行同样的操作

如果客户端调用 DepartmentgetBoss 方法,结果将被包装在 Optional 中,如例 6-14 所示。

例 6-14 返回 Optional

  1. Manager mrSlate = new Manager("Mr. Slate");
  2.  
  3. Department d = new Department();
  4. d.setBoss(mrSlate);
  5. System.out.println("Boss: " + d.getBoss());
  6.  
  7. Department d1 = new Department();
  8. System.out.println("Boss: " + d1.getBoss());

Department 中存在非空 Manager

❷ 打印 Boss: Optional[Manager{name='Mr. Slate'}]

Department 中不存在 Manager

❹ 打印 Boss: Optional.empty

截至目前一切顺利。如果 Department 中存在 Manager,getter 方法将其包装在 Optional 中并返回;如果 Department 中不存在 Manager,getter 方法将返回一个空 Optional

问题在于,无法通过在 Optional 上调用 getName 方法来获取 Managername。我们要么从 Optional 中取出包含的值,要么使用 map 方法(例 6-15)。

例 6-15 从 OptionalManager 中提取 name

  1. System.out.println("Name: " +
  2. d.getBoss().orElse(new Manager("Unknown")).getName());
  3.  
  4. System.out.println("Name: " +
  5. d1.getBoss().orElse(new Manager("Unknown")).getName());
  6.  
  7. System.out.println("Name: " + d.getBoss().map(Manager::getName));
  8. System.out.println("Name: " + d1.getBoss().map(Manager::getName));

❶ 在调用 getName 方法之前从 Optional 中提取 boss

❷ 利用 map 方法将 getName 应用于包含的 Manager

仅当 map 方法所调用的 Optional 不为空时,该方法才会应用给定函数,因此这种方案更为简单。map 方法的详细讨论请参见范例 6.5。

不过,如果多个 Optional 链接在一起,情况将变得较为复杂。例 6-16 定义了一个名为 Company 的类,它只包含一个 Department(为简单起见,只考虑一个 Department 的情况)。

例 6-16 只包含一个 DepartmentCompany

  1. public class Company {
  2. private Department department;
  3.  
  4. public Optional<Department> getDepartment() {
  5. return Optional.ofNullable(department);
  6. }
  7.  
  8. public void setDepartment(Department department) {
  9. this.department = department;
  10. }
  11. }

如果在 Company 类上调用 getDepartment 方法,结果将被包装在 Optional 中。为获取 Manager 的信息,似乎可以采用例 6-15 讨论的 map 方法,但这会导致一个 Optional 被包装在另一个 Optional 中,如例 6-17 所示。

例 6-17 包装在另一个 Optional 中的 Optional

  1. Company co = new Company();
  2. co.setDepartment(d);
  3.  
  4. System.out.println("Company Dept: " + co.getDepartment());
  5.  
  6. System.out.println("Company Dept Manager: " + co.getDepartment()
  7. .map(Department::getBoss));

❶ 打印 Company Dept: Optional[Department{boss=Manager{name='Mr.Slate'}}]

❷ 打印 Company Dept Manager: Optional[Optional[Manager{name='Mr.Slate'}]]

为解决这个问题,不妨使用 Optional.flatMap 方法。flatMap 方法可以将结构“展平”,因此只会返回一个 Optional。我们仍然按之前的方式创建 Company 类,然后应用 flatMap 方法,如例 6-18 所示。

例 6-18 在 Company 上应用 flatMap

  1. System.out.println(
  2. co.getDepartment()
  3. .flatMap(Department::getBoss)
  4. .map(Manager::getName));

Optional

Optional

Optional

接下来,将 Company 也包装在 Optional 中,如例 6-19 所示。

例 6-19 在 OptionalCompany 上应用 flatMap

  1. Optional<Company> company = Optional.of(co);
  2. System.out.println(
  3. company
  4. .flatMap(Company::getDepartment)
  5. .flatMap(Department::getBoss)
  6. .map(Manager::getName)
  7. );

Optional

Optional

Optional

Optional

不难看到,我们甚至可以将 Company 包装在 Optional 中,然后重复执行 flatMap 操作就能获取任何所需的属性,最后通过 map 操作结束。

另见

有关在 Optional 中包装值的讨论请参见范例 6.1,有关 Stream.flatMap 方法的讨论请参见范例 3.11,有关在 DAO 层中应用 Optional 的讨论请参见范例 6.3,有关 Optional.map 方法的讨论请参见范例 6.5。