Java Optional.flatMap与Optional.map方法,用户希望避免将一个 Optional
包装在另一个 Optional
中,使用 Optional
类定义的 flatMap
方法,有关 Stream
接口定义的 map
和 flatMap
方法请参见范例使用flatMap与map方法。
Java Optional.flatMap与Optional.map方法 问题描述
用户希望避免将一个 Optional
包装在另一个 Optional
中。
Java Optional.flatMap与Optional.map方法 解决方案
使用 Optional
类定义的 flatMap
方法。
Java Optional.flatMap与Optional.map方法 具体实例
有关 Stream
接口定义的 map
和 flatMap
方法请参见范例使用flatMap与map方法。不过 flatMap
是一个通用的概念,同样可以用于 Optional
。
Optional.flatMap
方法的签名如下:
<U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper)
Optional.flatMap
方法的签名与 Optional.map
方法类似,二者的 Function
参数应用于每个元素并生成一个结果,返回类型为 Optional<U>
。具体而言,如果参数 T
存在,Optional.flatMap
方法将函数应用于 T
并返回 Optional
,它包装包含的值;如果 T
不存在,方法将返回一个空 Optional
。
根据范例getter和setter方法中的Optional的讨论,数据访问对象(DAO)通常采用返回 Optional
的 getter 方法编写(如果属性可以为 null
),但 setter 方法不会将参数包装在 Optional
中。例 6-13 显示了两个类,一个是 Manager
类,它包含非空字符串 name
;另一个是 Department
类,它包含可空 Manager
特性 boss
。
例 6-13 包含
Optional
的 DAO 层(部分)
public class Manager {
private String name; ➊
public Manager(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
public class Department {
private Manager boss; ➋
public Optional<Manager> getBoss() {
return Optional.ofNullable(boss);
}
public void setBoss(Manager boss) {
this.boss = boss;
}
}
❶ 假定不为 null
,因此不需要 Optional
❷ 可能为 null
,因此在 Optional
中包装 getter 方法并返回,但不要对 setter 方法执行同样的操作
如果客户端调用 Department
的 getBoss
方法,结果将被包装在 Optional
中,如例 6-14 所示。
例 6-14 返回
Optional
Manager mrSlate = new Manager("Mr. Slate");
Department d = new Department();
d.setBoss(mrSlate); ➊
System.out.println("Boss: " + d.getBoss()); ➋
Department d1 = new Department(); ➌
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
方法来获取 Manager
的 name
。我们要么从 Optional
中取出包含的值,要么使用 map
方法(例 6-15)。
例 6-15 从
Optional
的Manager
中提取name
System.out.println("Name: " +
d.getBoss().orElse(new Manager("Unknown")).getName()); ➊
System.out.println("Name: " +
d1.getBoss().orElse(new Manager("Unknown")).getName());
System.out.println("Name: " + d.getBoss().map(Manager::getName)); ➋
System.out.println("Name: " + d1.getBoss().map(Manager::getName));
❶ 在调用 getName
方法之前从 Optional
中提取 boss
❷ 利用 map
方法将 getName
应用于包含的 Manager
仅当 map
方法所调用的 Optional
不为空时,该方法才会应用给定函数,因此这种方案更为简单。map
方法的详细讨论请参见范例Optional的映射。
不过,如果多个 Optional
链接在一起,情况将变得较为复杂。例 6-16 定义了一个名为 Company
的类,它只包含一个 Department
(为简单起见,只考虑一个 Department
的情况)。
例 6-16 只包含一个
Department
的Company
public class Company {
private Department department;
public Optional<Department> getDepartment() {
return Optional.ofNullable(department);
}
public void setDepartment(Department department) {
this.department = department;
}
}
如果在 Company
类上调用 getDepartment
方法,结果将被包装在 Optional
中。为获取 Manager
的信息,似乎可以采用例 6-15 讨论的 map
方法,但这会导致一个 Optional
被包装在另一个 Optional
中,如例 6-17 所示。
例 6-17 包装在另一个
Optional
中的Optional
Company co = new Company();
co.setDepartment(d);
System.out.println("Company Dept: " + co.getDepartment()); ➊
System.out.println("Company Dept Manager: " + co.getDepartment()
.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
System.out.println(
co.getDepartment() ➊
.flatMap(Department::getBoss) ➋
.map(Manager::getName)); ➌
❶ Optional<Department>
❷ Optional<Manager>
❸ Optional<String>
接下来,将 Company
也包装在 Optional
中,如例 6-19 所示。
例 6-19 在
Optional
的Company
上应用flatMap
Optional<Company> company = Optional.of(co);
System.out.println(
company ➊
.flatMap(Company::getDepartment) ➋
.flatMap(Department::getBoss) ➌
.map(Manager::getName) ➍
);
❶ Optional<Company>
❷ Optional<Department>
❸ Optional<Manager>
❹ Optional<String>
不难看到,我们甚至可以将 Company
包装在 Optional
中,然后重复执行 flatMap
操作就能获取任何所需的属性,最后通过 map
操作结束。