Optional 详解
周五下午,飞翔科技的代码评审会上,小崔的一段代码引起了争议。
Employee emp = employeeDao.findById(1001); String dept = emp.getDepartment().getName();大翔皱了皱眉:"小崔,如果员工不存在呢?getDepartment() 返回 null 呢?"
小崔自信地说:"我前面判断了 if (emp != null) 啊——啊等等,我把那个 PR 里的 null 检查给删了..."
白歌叹气:"这就是 NullPointerException 的由来。Tony Hoare 称 null 是他'十亿美元的错误'。"
朱璐好奇:"那 JDK 8 的 Optional 真的能消除 NPE 吗?"
大翔笑道:"Optional 不是万能药,但用好了能让代码健壮性提升一个档次。今天就讲讲 Optional 的正确打开方式。"
一、定义表
| 方法 | 分类 | 描述 | 返回值 |
|---|---|---|---|
Optional.empty() | 创建 | 创建一个空的 Optional | Optional<T> |
Optional.of(T value) | 创建 | 包装非 null 值,value 为 null 则抛 NPE | Optional<T> |
Optional.ofNullable(T value) | 创建 | 包装值,value 可为 null(自动变为 empty) | Optional<T> |
get() | 取值 | 获取值,若为空抛 NoSuchElementException | T |
isPresent() | 判断 | 是否存在值 | boolean |
ifPresent(Consumer<T>) | 消费 | 值存在时执行操作 | void |
orElse(T other) | 默认值 | 存在返回值,否则返回 other | T |
orElseGet(Supplier<T>) | 惰性默认值 | 存在返回值,否则执行 Supplier 获取 | T |
orElseThrow(Supplier<X>) | 异常处理 | 存在返回值,否则抛Supplier产生的异常 | T |
map(Function<T, R>) | 转换 | 值存在时转换,返回 Optional<R> | Optional<R> |
flatMap(Function<T, Optional<R>>) | 扁平转换 | 值存在时转换,避免嵌套 Optional | Optional<R> |
filter(Predicate<T>) | 过滤 | 值满足条件时保留,否则变 empty | Optional<T> |
二、Mermaid 流程图:Optional 值处理管道
三、Mermaid 流程图:Optional 方法选择决策树
四、创建 Optional 的正确方式
4.1 三种创建方法对比
// 1. of —— 明确知道非 null
Optional<String> opt1 = Optional.of("飞翔科技"); // OK
// Optional<String> opt2 = Optional.of(null); // ← NullPointerException!
// 2. ofNullable —— 值可能为 null(最常用)
Optional<String> opt3 = Optional.ofNullable(null); // 返回 Optional.empty
Optional<String> opt4 = Optional.ofNullable("大翔"); // 返回 Optional[大翔]
// 3. empty —— 明确返回空
Optional<String> opt5 = Optional.empty(); // 返回 Optional.empty
4.2 最佳实践:API 返回值设计
// ✓ 正确:DAO 方法返回 Optional 明确表达"可能没有结果"
public Optional<Employee> findById(int id) {
Employee emp = database.get(id);
return Optional.ofNullable(emp);
}
// ✗ 错误:不要返回 null Optional
public Optional<Employee> findById(int id) {
if (id <= 0) return null; // ← 绝对不要!
return Optional.ofNullable(database.get(id));
}
五、orElse 与 orElseGet 的致命区别
这是 Optional 最容易踩的坑!看代码:
// 假设有一个昂贵的操作
public String computeExpensiveDefault() {
System.out.println("执行了昂贵的默认值计算...");
return "默认值";
}
Optional<String> opt = Optional.of("飞翔科技");
// orElse:无论 Optional 是否有值,都会执行参数表达式!
String result1 = opt.orElse(computeExpensiveDefault());
// 输出:执行了昂贵的默认值计算... ← 即使 opt 有值也执行了!
// orElseGet:只有 Optional 为空时才执行
String result2 = opt.orElseGet(() -> computeExpensiveDefault());
// 无输出 ← opt 有值,Supplier 不会执行
记忆法则:
orElse(T)—— eager 求值,参数在调用前已计算完毕orElseGet(Supplier<T>)—— lazy 求值,仅在需要时执行
六、map 与 flatMap 的图文对比
class Employee {
private Department department;
public Optional<Department> getDepartment() { // 返回 Optional
return Optional.ofNullable(department);
}
}
class Department {
private String name;
public String getName() { return name; }
}
// 场景:获取员工所在部门名称
Optional<Employee> optEmp = Optional.ofNullable(findEmployee(1001));
// map:直接转换 → 返回 Optional<String>
Optional<String> deptName = optEmp
.map(Employee::getDepartment) // Optional<Optional<Department>> ← 双重包装!
.map(dept -> dept.getName()); // 编译错误!dept 是 Optional<Department> 不是 Department
// flatMap:扁平化转换 → 返回 Optional<String>
Optional<String> deptName = optEmp
.flatMap(Employee::getDepartment) // Optional<Department> ← 扁平化!
.map(Department::getName); // Optional<String> ← 正常工作
七、完整代码示例
示例1:飞翔科技员工查询系统防 NPE
import java.util.*;
import java.util.function.Supplier;
/**
* 飞翔科技员工查询系统 —— Optional 实战
* 演示如何用 Optional 优雅处理多层嵌套的 null 安全问题
*/
// --- 实体类(模拟复杂嵌套结构)---
class Employee {
private Integer id;
private String name;
private Department department; // 可能为 null
private Office office; // 可能为 null
private Double salary;
public Employee(Integer id, String name, Department department, Office office, Double salary) {
this.id = id; this.name = name; this.department = department;
this.office = office; this.salary = salary;
}
public Integer getId() { return id; }
public String getName() { return name; }
// 返回 Optional 明确表达可能为 null
public Optional<Department> getDepartment() { return Optional.ofNullable(department); }
public Optional<Office> getOffice() { return Optional.ofNullable(office); }
public Optional<Double> getSalary() { return Optional.ofNullable(salary); }
@Override
public String toString() {
return String.format("员工[%d, %s, 薪资¥%s]",
id, name, salary != null ? String.format("%.2f", salary) : "未知");
}
}
class Department {
private String deptName;
private String leader; // 部门负责人,可能为 null
public Department(String deptName, String leader) {
this.deptName = deptName; this.leader = leader;
}
public String getDeptName() { return deptName; }
public Optional<String> getLeader() { return Optional.ofNullable(leader); }
}
class Office {
private String building;
private Integer floor;
private String city;
public Office(String building, Integer floor, String city) {
this.building = building; this.floor = floor; this.city = city;
}
public String getBuilding() { return building; }
public Integer getFloor() { return floor; }
public String getCity() { return city; }
public String getFullAddress() {
return city + "市 " + building + " " + floor + "层";
}
}
// --- 模拟数据库 ---
class EmployeeRepository {
private static Map<Integer, Employee> DB = new LinkedHashMap<>();
static {
DB.put(1001, new Employee(1001, "大翔",
new Department("研发部", "Frank"),
new Office("飞翔大厦A座", 15, "北京"), 18888.88));
DB.put(1002, new Employee(1002, "白歌",
new Department("研发部", null), // 部门负责人为空
new Office("飞翔大厦A座", 12, "深圳"), 15888.88));
DB.put(1003, new Employee(1003, "小崔",
new Department("研发部", "Frank"),
null, // 办公室信息为空
8888.88));
DB.put(1004, new Employee(1004, "朱璐", null, null, null)); // 几乎全空
// ID 1005 不存在
}
public Optional<Employee> findById(int id) {
return Optional.ofNullable(DB.get(id));
}
public List<Employee> findAll() {
return new ArrayList<>(DB.values());
}
}
public class OptionalDemo {
public static void main(String[] args) {
System.out.println("========== 飞翔科技员工查询系统 ==========\n");
EmployeeRepository repo = new EmployeeRepository();
// ===== 场景1:基本查询 + orElse =====
System.out.println("【场景1:基本查询 orElse / orElseThrow】");
Employee emp1 = repo.findById(1001)
.orElseThrow(() -> new RuntimeException("员工不存在!"));
System.out.println(" 找到: " + emp1);
Employee defaultEmp = repo.findById(9999)
.orElse(new Employee(-1, "默认员工", null, null, 0.0));
System.out.println(" 未找到ID 9999,返回默认: " + defaultEmp);
// ===== 场景2:map 链式访问多层嵌套 =====
System.out.println("\n【场景2:map 安全访问嵌套属性】");
// 传统方式(易NPE):
// String leader = emp.getDepartment().getLeader(); ← 任一环节为null就炸
// Optional 方式:获取大翔的部门负责人
String leader1001 = repo.findById(1001)
.flatMap(Employee::getDepartment)
.flatMap(Department::getLeader)
.orElse("未指定负责人");
System.out.println(" 大翔的部门负责人: " + leader1001);
// 白歌的部门负责人为 null
String leader1002 = repo.findById(1002)
.flatMap(Employee::getDepartment)
.flatMap(Department::getLeader)
.orElse("未指定负责人");
System.out.println(" 白歌的部门负责人: " + leader1002);
// 朱璐没有部门
String leader1004 = repo.findById(1004)
.flatMap(Employee::getDepartment)
.flatMap(Department::getLeader)
.orElse("未指定负责人");
System.out.println(" 朱璐的部门负责人: " + leader1004);
// ===== 场景3:filter 条件过滤 =====
System.out.println("\n【场景3:filter 条件过滤 + ifPresent】");
repo.findById(1001)
.filter(e -> e.getSalary().orElse(0.0) > 15000)
.ifPresent(e -> System.out.println(" ✓ " + e.getName() + " 薪资 > 15000,符合晋升条件"));
repo.findById(1003)
.filter(e -> e.getSalary().orElse(0.0) > 15000)
.ifPresent(e -> System.out.println(" ✓ 符合晋升条件"));
// 小崔薪资8888 < 15000,filter后变empty,ifPresent不执行
// ===== 场景4:orElseGet 惰性求值 =====
System.out.println("\n【场景4:orElse vs orElseGet】");
String val1 = repo.findById(1001)
.map(Employee::getName)
.orElse(expensiveFallback());
System.out.println(" orElse 结果: " + val1 + "(注意:expensiveFallback 还是执行了!)\n");
String val2 = repo.findById(1001)
.map(Employee::getName)
.orElseGet(() -> expensiveFallback());
System.out.println(" orElseGet 结果: " + val2 + "(expensiveFallback 没有执行 ✓)");
// ===== 场景5:ifPresent 安全消费 =====
System.out.println("\n【场景5:ifPresent 安全消费】");
System.out.print(" 员工 1001 的办公室: ");
repo.findById(1001)
.flatMap(Employee::getOffice)
.map(Office::getFullAddress)
.ifPresent(System.out::println);
System.out.print(" 员工 1003 的办公室: ");
repo.findById(1003)
.flatMap(Employee::getOffice)
.map(Office::getFullAddress)
.ifPresentOrElse(
System.out::println,
() -> System.out.println("无办公室信息") // JDK 9+ 才有 ifPresentOrElse
);
// JDK 8 替代方案:
Optional<String> office1003 = repo.findById(1003)
.flatMap(Employee::getOffice)
.map(Office::getFullAddress);
if (office1003.isPresent()) {
System.out.println(office1003.get());
} else {
System.out.println("无办公室信息");
}
// ===== 场景6:Stream + Optional 联动 =====
System.out.println("\n【场景6:Stream + Optional 联动】");
List<Employee> allEmps = repo.findAll();
System.out.println(" 所有员工薪资汇总:");
double totalSalary = allEmps.stream()
.map(Employee::getSalary)
.filter(Optional::isPresent)
.mapToDouble(Optional::get)
.sum();
System.out.printf(" 总薪资池: ¥%.2f\n", totalSalary);
// 更优雅写法
double totalSalary2 = allEmps.stream()
.flatMap(e -> e.getSalary()
.map(Stream::of)
.orElseGet(Stream::empty))
.mapToDouble(Double::doubleValue)
.sum();
System.out.printf(" 总薪资池(flatMap): ¥%.2f\n", totalSalary2);
System.out.println("\n========== End ==========");
}
private static String expensiveFallback() {
System.out.println(" [expensiveFallback] 执行了昂贵的查询...");
return "Fallback-Value";
}
}
运行输出:
========== 飞翔科技员工查询系统 ==========
【场景1:基本查询 orElse / orElseThrow】
找到: 员工[1001, 大翔, 薪资¥18888.88]
未找到ID 9999,返回默认: 员工[-1, 默认员工, 薪资¥0.00]
【场景2:map 安全访问嵌套属性】
大翔的部门负责人: Frank
白歌的部门负责人: 未指定负责人
朱璐的部门负责人: 未指定负责人
【场景3:filter 条件过滤 + ifPresent】
✓ 大翔 薪资 > 15000,符合晋升条件
【场景4:orElse vs orElseGet】
[expensiveFallback] 执行了昂贵的查询...
orElse 结果: 大翔(注意:expensiveFallback 还是执行了!)
orElseGet 结果: 大翔(expensiveFallback 没有执行 ✓)
【场景5:ifPresent 安全消费】
员工 1001 的办公室: 北京市 飞翔大厦A座 15层
员工 1003 的办公室: 无办公室信息
【场景6:Stream + Optional 联动】
所有员工薪资汇总:
总薪资池: ¥46666.64
总薪资池(flatMap): ¥46666.64
========== End ==========
示例2:飞翔科技配置读取安全处理
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* 飞翔科技应用配置管理器 —— Optional 防 NPE 实战
*/
// 模拟配置层级:系统配置 → 用户配置 → 默认配置
class AppConfig {
// 模拟配置文件
private static Map<String, Map<String, String>> config = new LinkedHashMap<>();
static {
Map<String, String> dbConfig = new LinkedHashMap<>();
dbConfig.put("host", "192.168.1.100");
dbConfig.put("port", "3306");
dbConfig.put("user", "feixiang_admin");
// 故意不设置 password
Map<String, String> appConfig = new LinkedHashMap<>();
appConfig.put("app.name", "飞翔科技OA系统");
appConfig.put("app.version", "3.2.1");
appConfig.put("app.maxUploadSize", "50");
// 故意不设置 app.timeout
Map<String, String> featureFlags = new LinkedHashMap<>();
featureFlags.put("feature.nightMode", "true");
featureFlags.put("feature.aiAssistant", "false");
featureFlags.put("feature.darkTheme", null); // null 值
config.put("database", dbConfig);
config.put("application", appConfig);
config.put("features", featureFlags);
}
/**
* 安全获取配置值
*/
public static Optional<String> getConfig(String group, String key) {
return Optional.ofNullable(config.get(group))
.map(m -> m.get(key));
}
/**
* 获取整型配置
*/
public static Optional<Integer> getIntConfig(String group, String key) {
return getConfig(group, key)
.flatMap(val -> {
try {
return Optional.of(Integer.parseInt(val));
} catch (NumberFormatException e) {
return Optional.empty();
}
});
}
/**
* 获取布尔配置
*/
public static boolean getBooleanConfig(String group, String key, boolean defaultValue) {
return getConfig(group, key)
.map(String::toLowerCase)
.map(val -> val.equals("true") || val.equals("1"))
.orElse(defaultValue);
}
}
public class ConfigDemo {
public static void main(String[] args) {
System.out.println("========== 飞翔科技配置系统 ==========\n");
// ===== 1. 基本配置读取 =====
System.out.println("【1> 数据库配置】");
System.out.println(" host: " + AppConfig.getConfig("database", "host").orElse("localhost"));
System.out.println(" port: " + AppConfig.getIntConfig("database", "port").orElse(3306));
System.out.println(" user: " + AppConfig.getConfig("database", "user").orElse("root"));
// 不存在的配置
System.out.println(" password: " + AppConfig.getConfig("database", "password")
.orElseGet(() -> {
System.out.println(" ⚠ 数据库密码未配置,使用默认密码");
return "feixiang_default_pwd";
}));
// ===== 2. 配置不存在时的优雅降级 =====
System.out.println("\n【2> 应用配置 - 降级处理】");
String name = AppConfig.getConfig("application", "app.name")
.orElse("未命名应用");
System.out.println(" 应用名: " + name);
int maxUpload = AppConfig.getIntConfig("application", "app.maxUploadSize")
.filter(size -> size > 0 && size <= 500)
.orElse(10); // 超出范围或未配则默认10MB
System.out.println(" 最大上传: " + maxUpload + " MB");
// timeout 未配置 → 使用多层降级
int timeout = AppConfig.getIntConfig("application", "app.timeout")
.orElse(30); // 未配置则默认30秒
System.out.println(" 超时时间: " + timeout + " s (使用默认值)");
// ===== 3. 特性开关 =====
System.out.println("\n【3> 特性开关】");
System.out.println(" 夜间模式: " +
AppConfig.getBooleanConfig("features", "feature.nightMode", false));
System.out.println(" AI助手: " +
AppConfig.getBooleanConfig("features", "feature.aiAssistant", true));
// darkTheme 值为 null → 走默认值
System.out.println(" 暗黑主题: " +
AppConfig.getBooleanConfig("features", "feature.darkTheme", false));
// ===== 4. 验证必需配置 =====
System.out.println("\n【4> 必需配置验证】");
try {
String dbHost = AppConfig.getConfig("database", "host")
.filter(h -> !h.isEmpty())
.orElseThrow(() -> new IllegalStateException(
"致命错误:数据库主机地址未配置!"));
System.out.println(" ✓ 数据库主机已配置: " + dbHost);
} catch (IllegalStateException e) {
System.out.println(" ✗ " + e.getMessage());
}
// 验证不存在的必需配置
try {
AppConfig.getConfig("database", "sslCert")
.orElseThrow(() -> new IllegalStateException(
"致命错误:SSL证书路径未配置!"));
} catch (IllegalStateException e) {
System.out.println(" ✗ " + e.getMessage());
}
// ===== 5. 演示传统代码 vs Optional 代码(同一逻辑) =====
System.out.println("\n【5> 传统 null 检查 vs Optional 链式调用】");
String group = "database";
String key = "host";
// 传统方式:嵌套 if-else
String traditionalResult = null;
Map<String, String> groupConfig = AppConfig.config.get(group);
if (groupConfig != null) {
String value = groupConfig.get(key);
if (value != null) {
traditionalResult = value;
} else {
traditionalResult = "localhost";
}
} else {
traditionalResult = "localhost";
}
System.out.println(" 传统方式结果: " + traditionalResult);
// Optional 方式
String optionalResult = Optional.ofNullable(AppConfig.config.get(group))
.map(m -> m.get(key))
.orElse("localhost");
System.out.println(" Optional方式: " + optionalResult);
System.out.println("\n========== 配置加载完成 ==========");
}
}
运行输出:
========== 飞翔科技配置系统 ==========
【1> 数据库配置】
host: 192.168.1.100
port: 3306
user: feixiang_admin
⚠ 数据库密码未配置,使用默认密码
password: feixiang_default_pwd
【2> 应用配置 - 降级处理】
应用名: 飞翔科技OA系统
最大上传: 50 MB
超时时间: 30 s (使用默认值)
【3> 特性开关】
夜间模式: true
AI助手: false
暗黑主题: false
【4> 必需配置验证】
✓ 数据库主机已配置: 192.168.1.100
✗ 致命错误:SSL证书路径未配置!
【5> 传统 null 检查 vs Optional 链式调用】
传统方式结果: 192.168.1.100
Optional方式: 192.168.1.100
========== 配置加载完成 ==========
八、易错场景
易错1:将 Optional 用作字段类型
// ✗ 错误:不要这样做!
public class Employee {
private Optional<String> name; // ← 不要用 Optional 做字段!
private Optional<Department> dept; // ← 不要!
}
// ✓ 正确:字段还是普通引用,getter 可以返回 Optional
public class Employee {
private String name;
private Department department;
public Optional<String> getName() { return Optional.ofNullable(name); }
public Optional<Department> getDepartment() { return Optional.ofNullable(department); }
}
// 原因:
// 1. Optional 不是 Serializable(虽然可以实现,但不是设计目标)
// 2. Optional 不应作为字段会增加内存开销
// 3. 违反了 Optional 的设计初衷——它应该作为返回值类型
易错2:将 Optional 用作方法参数
// ✗ 错误:不要这样做!
public void processEmployee(Optional<Employee> empOpt) {
empOpt.ifPresent(emp -> { /* ... */ });
}
// 调用方被迫做:processEmployee(Optional.ofNullable(emp))
// ✓ 正确:参数用原始类型,必要时在方法内部包装
public void processEmployee(Employee emp) {
Optional.ofNullable(emp).ifPresent(e -> { /* ... */ });
}
易错3:在集合中使用 Optional
// ✗ 错误:不要将 Optional 放入集合
List<Optional<String>> list = new ArrayList<>(); // ← 错误
Map<String, Optional<Integer>> map = new HashMap<>(); // ← 错误
// ✓ 正确:使用空集合、null 或专门的空对象
List<String> list = new ArrayList<>(); // 空 List 表示无值
Map<String, Integer> map = new HashMap<>(); // 不含 key 即无值
易错4:对空 Optional 调用 get() 而不检查
// ✗ 危险:
Optional<String> opt = Optional.empty();
String val = opt.get(); // 会抛出 NoSuchElementException!
// ✓ 安全:
String val = opt.orElse("默认值");
// ✓ JDK 10+ 替代:opt.orElseThrow() 能更清晰表达意图
// 但在 JDK 8 中只能用 opt.orElseThrow(SomeException::new)
易错5:orElse 中执行副作用操作
// ✗ 错误:orElse 中的代码总是执行,无论 Optional 是否为空
Optional.of("大翔").orElse(logAndReturn("不存在")); // logAndReturn 必执行!
// ✓ 正确:
Optional.of("大翔").orElseGet(() -> logAndReturn("不存在")); // 仅在空时执行
九、面试考点
考点1:Optional 的设计初衷是什么?它解决了什么问题?
答: Optional 是 JDK 8 引入的一个容器类,代表一个值可能存在也可能不存在。其核心目的是:
- 强制 API 使用者考虑 null 情况:返回 Optional 的方法明确告诉调用者"结果可能为空",强迫程序员显式处理。
- 消除 NPE:通过链式 API(map/flatMap/filter/orElse)安全地处理嵌套 null。
- 函数式编程风格:避免深层嵌套的 if-else 判空代码,使代码更加声明式。
但 Optional 不是万能药——它不能也不应该完全替代 null,而是用于特定场景(主要是方法的返回值)。
考点2:orElse 和 orElseGet 有什么区别?如何选择?
答:
| orElse(T) | orElseGet(Supplier<T>) | |
|---|---|---|
| 参数类型 | 值 | 函数式接口 |
| 求值时机 | Eager(立即求值) | Lazy(惰性求值) |
| 性能 | 默认值总是被构造 | 仅在需要时构造 |
| 适用场景 | 默认值是常量或已存在的对象 | 默认值需要计算、查询数据库、耗时操作 |
| 副作用 | 参数表达式总是执行 | 仅在 Optional 为空时执行 |
// 简单常量 → orElse
.orElse("默认值")
// 需要计算 → orElseGet
.orElseGet(() -> db.queryFallback()) // 仅在空时才查库
考点3:Optional 的 map 和 flatMap 有什么区别?
答:
map(Function<T, R>):对 Optional 内的值进行普通转换。如果函数返回 Optional,会导致Optional<Optional<R>>的双重包装。flatMap(Function<T, Optional<R>>):对 Optional 内的值进行扁平化转换。函数的返回值本身就是 Optional,flatMap 会自动"解包",避免双重包装。
// map 场景:简单类型转换
Optional<String> upper = optName.map(String::toUpperCase);
// flatMap 场景:链式访问可能为 null 的属性
Optional<String> leader = optEmp
.flatMap(Employee::getDepartment) // getDepartment() 返回 Optional<Department>
.flatMap(Department::getLeader); // getLeader() 返回 Optional<String>
考点4:Optional 有哪些使用上的反模式(Anti-Pattern)?
答:
- 不要用作字段:Optional 设计为返回值容器,不应作为类的成员变量。
- 不要用作方法参数:迫使调用方包装参数,增加不必要的复杂度。
- 不要用在集合中:如
List<Optional<T>>——直接用空 List 或 filter 可达到同样效果。 - 不要对 Optional 做 null 检查:返回 null Optional 违背了 Optional 的本意——调用方会再次面对 NPE。应返回
Optional.empty()。 - 不要调用
get()而不检查:get()是"危险"操作,应始终配合isPresent()或直接用orElse/orElseGet/orElseThrow。 - 不要对集合用
Optional.ofNullable()包装:空集合本身就可以表达"无数据",应直接返回空集合而非Optional<List<T>>。
大翔合上笔记本总结:"Optional 的核心哲学就一句话——让 API 的返回类型自己说话。你看到
Optional<Employee>,就知道这个结果可能为空,必须处理。"白歌补充:"记住三个原则:1) Optional 只做返回值,不做字段和参数;2) 默认值计算用 orElseGet 而不是 orElse;3) 嵌套属性访问用 flatMap 而不是 map。"
小崔突然醒悟:"原来我之前写的
if (obj != null) { if (obj.getXxx() != null) { ... } }就是 Optional 要消灭的!"朱璐笑道:"对,这就是传说中的'金字塔厄运'(Pyramid of Doom)。"
黄俪(HR)在门口喊道:"你们能不能把员工档案查询系统的 NPE 都修一下?上周 Production 又炸了!"
众人异口同声:"用 Optional!"
JDK 8 Optional 限制(JDK 9/10/11 已解决,详见第17章「Optional 增强」)
JDK 8 的 Optional 有以下几个限制,在后续版本中陆续解决:
- 没有
ifPresentOrElse()→ JDK 9 加入(值存在/不存在双分支处理)- 没有
or()→ JDK 9 加入(链式备选 Optional,保持管道不中断)- 没有
stream()→ JDK 9 加入(Optional → Stream,配合flatMap优雅过滤空值)orElseThrow()必须传异常 Supplier → JDK 10 支持无参(默认 NoSuchElementException)- 没有
isEmpty()→ JDK 11 加入(比!isPresent()更自然、更可读)在 JDK 8 中,这些需求需要通过
isPresent()+get()或额外工具方法来实现。升级到 JDK 11 后,建议优先使用上述增强方法。