Optional设计原理
# 24.Optional设计原理
# 目录介绍
- 1. 案例引入
- 2. 十亿美元错误
- 3. Optional 类剖析
- 4. 核心 API 全景
- 5. 函数式链路
- 6. 取值方法陷阱
- 7. 使用边界规约
- 8. 设计权衡之谜
- 9. 实战重构套路
- 10. 综合案例串讲
# 1. 案例引入
# 1.1 千层嵌套的判空
某团队代码评审时发现一段"金字塔判空",所有人都看吐了:
public class OrderService {
public String getReceiverCity(Long orderId) {
Order order = orderDao.findById(orderId);
if (order != null) {
User user = order.getUser();
if (user != null) {
Address address = user.getAddress();
if (address != null) {
City city = address.getCity();
if (city != null) {
String name = city.getName();
if (name != null) {
return name.toUpperCase();
}
}
}
}
}
return "UNKNOWN";
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
问题:
- ❌ 6 层嵌套,圈复杂度爆表
- ❌ 业务逻辑被判空淹没
- ❌ 漏判一层就是 NPE
- ❌ 代码量是逻辑量的 5 倍
重构后:
public String getReceiverCity(Long orderId) {
return Optional.ofNullable(orderDao.findById(orderId))
.map(Order::getUser)
.map(User::getAddress)
.map(Address::getCity)
.map(City::getName)
.map(String::toUpperCase)
.orElse("UNKNOWN");
}
2
3
4
5
6
7
8
9
疑惑:
- 为什么
map链式调用能"自动跳过 null"? - 中间某一步返回
Optional<X>时该用flatMap还是map? orElse("UNKNOWN")和orElseGet(() -> "UNKNOWN")哪个性能更好?
# 1.2 Optional 的反向滥用
另一个团队听说 Optional 好用,开始"全面拥抱",结果走向了反面:
// ❌ 反例 1:Optional 当字段
public class User {
private Long id;
private Optional<String> nickname; // ★ 字段用 Optional
private Optional<Address> address; // ★ 字段用 Optional
// 序列化报错:Optional 不实现 Serializable!
}
// ❌ 反例 2:Optional 当参数
public List<Order> queryOrders(
Optional<Long> userId, // ★ 参数用 Optional
Optional<Status> status,
Optional<LocalDate> startDate
) {
// 调用方:queryOrders(Optional.of(123L), Optional.empty(), Optional.empty())
// 比传 null 还啰嗦!
}
// ❌ 反例 3:集合元素是 Optional
List<Optional<User>> users = userIds.stream()
.map(id -> Optional.ofNullable(userDao.findById(id)))
.collect(Collectors.toList());
// 调用方还要再 isPresent 一遍
// ❌ 反例 4:Optional 嵌套
Optional<Optional<String>> nested = ...; // ★ 反智写法
// ❌ 反例 5:用 Optional 后又 get
String name = optionalUser.get().getName(); // ★ 等价 NPE,且更难定位
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
疑惑:
- 为什么
Optional不能当字段? - 为什么 JDK 设计者明确说"
Optional不要做参数"? - "用了
Optional反而更糟"的边界在哪里?
5 大追问汇总:
追问 ①:NPE 是怎么诞生的?为什么各语言都在解决它? → 第2章
追问 ②:Optional 内部到底是什么? → 第3章
追问 ③:map / flatMap / filter 怎么实现"短路"? → 第5章
追问 ④:orElse 与 orElseGet 的本质区别? → 第6.2、6.3节
追问 ⑤:§1.2 的反模式根因是什么? → 第7、8章
2
3
4
5
# 1.3 我们要回答什么
第 29 篇要把"Optional 是什么、为什么这么设计、什么时候用什么时候不用"讲透——Optional 不是"NPE 检查器",而是一种"显式可空性"的类型契约:
Optional 的两层定位:
定位 ①:API 返回值的"可能不存在"契约
返回 Optional<User> ≠ 返回 User 也可能 null
Optional 强制调用方面对"不存在"这个状态
→ 把"可能为空"从隐含假设升级为类型签名
定位 ②:函数式链路的"短路容器"
容器中有值 → map/filter/flatMap 正常处理
容器为空 → 整条链短路,直接到 orElse
→ 与 Stream 的设计哲学一脉相承
2
3
4
5
6
7
8
9
10
11
本篇路线:
十亿美元错误 (第2章) ─── 历史与背景
↓
Optional 类剖析 (第3章) ←—— 内部数据结构
↓
核心 API 全景 (第4章) ←—— 方法分类与签名
↓
函数式链路 (第5章) ←—— map/flatMap/filter
↓
取值方法陷阱 (第6章) ←—— orElse vs orElseGet
↓
使用边界规约 (第7章) ←—— 用在哪儿、不用在哪儿
↓
设计权衡之谜 (第8章) ←—— 为什么不能 Serializable
↓
实战重构套路 (第9章) ←—— 真实重构案例
↓
综合案例串讲 (第10章)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 2. 十亿美元错误
# 2.1 Tony Hoare 自白
null 引用由计算机科学家 Tony Hoare 在 1965 年为 ALGOL W 语言引入,他在 2009 年的 QCon 大会上做了著名的"忏悔"演讲:
"I call it my billion-dollar mistake. It was the invention of the null reference in 1965. ... I couldn't resist the temptation to put in a null reference, simply because it was so easy to implement."
我称它为我的"十亿美元错误"——这就是 1965 年发明的 null 引用。我无法抵抗加入它的诱惑,因为实现它实在太简单了。
为什么是十亿美元?因为半个世纪以来:
- NPE 是生产环境头号 Bug 类型
- Java 60% 的运行时异常是 NPE
- 排查、测试、修复 NPE 的人力成本累计远超十亿美元
# 2.2 NPE 的传染性
null 在 Java 里有一个独特性质——它是任何引用类型的合法值:
String s = null; // ✅ 合法
List<Integer> list = null; // ✅ 合法
User u = null; // ✅ 合法
// 后果:任何返回引用类型的方法,调用方都不知道是否要判空
public User findUser(Long id) { ... } // 可能返回 null?必须看注释或源码
2
3
4
5
6
NPE 的三大特点:
特点 ①:传染性
null 一旦产生,会在调用链中传递
user.getAddress().getCity() 任意一层为 null 都会爆
特点 ②:延迟性
null 产生的位置和爆炸的位置往往很远
Service 返回 null → Controller 调用 → JSON 序列化报错
特点 ③:不可区分性
返回 null 的语义不明确:
是"没找到"?
是"出错了"?
是"还没加载"?
是"正常的空值"?
调用方无法区分
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 2.3 各语言的解法
不同语言对"可能不存在"的值采用了不同方案:
| 语言 | 解法 | 类型示例 |
|---|---|---|
| Haskell | Maybe 单子 | Maybe Int(值类型,编译期强制) |
| Scala | Option[T] | Option[String](Some/None) |
| Rust | Option<T> | Option<i32>(编译期强制处理) |
| Kotlin | 可空类型 | String? vs String(语法层区分) |
| Swift | Optional 类型 | String?(语法层区分) |
| TypeScript | 联合类型 | string \| undefined(编译期检查) |
| C# | Nullable<T> + 可空引用 | string?(C# 8 起) |
| Java 8+ | Optional<T> 类 | Optional<String>(库级方案) |
Kotlin 的"语法级"方案(最彻底):
val s1: String = null // ❌ 编译报错
val s2: String? = null // ✅ 显式声明可空
val len = s2.length // ❌ 编译报错(s2 可能为 null)
val len = s2?.length // ✅ 安全调用,结果是 Int?
val len = s2?.length ?: 0 // ✅ Elvis 操作符,提供默认值
2
3
4
5
# 2.4 Java 的折中方案
Java 受限于历史包袱(不能引入新语法、不能破坏 null 兼容),选择了库级方案——在 JDK 8 引入 java.util.Optional:
Java 的选择:
① 不修改语法(保留 null)
② 不引入可空类型(不影响现有代码)
③ 提供库类 Optional<T>,鼓励在返回值中使用
④ 配合 Stream API 设计(filter/map/flatMap 风格统一)
代价:
✗ 编译期不强制(可以照样返回 null)
✗ Optional 本身也可以是 null(!)
✗ 不是值类型(有对象开销)
✗ 不能与基本类型混用(OptionalInt/OptionalLong/OptionalDouble 单独存在)
2
3
4
5
6
7
8
9
10
11
这就是为什么我们要严格规约 Optional 的使用边界(第 7 章)——库级方案的局限性,必须靠规约弥补。
# 3. Optional 类剖析
# 3.1 类与字段结构
Optional 的核心非常简单——就是对一个 T value 字段的包装:
public final class Optional<T> {
// ★ 全局单例(empty)
private static final Optional<?> EMPTY = new Optional<>();
// ★ 包装的值
private final T value;
// ★ 私有构造(无值)
private Optional() {
this.value = null;
}
// ★ 私有构造(有值)
private Optional(T value) {
this.value = Objects.requireNonNull(value); // ★ of() 不允许 null
}
// ... 方法略
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
关键观察:
final class不允许继承(§8.2 解释)- 没有
setter,value用final修饰(不可变) - 不实现
Serializable(§8.1 解释) - 用
null表示"empty"——但用户感知不到
# 3.2 三种创建方式
// ① empty():创建空 Optional
Optional<String> empty = Optional.empty();
// ② of(T):值不允许为 null(违反则 NPE)
Optional<String> opt1 = Optional.of("hello");
Optional<String> opt2 = Optional.of(null); // ❌ NullPointerException
// ③ ofNullable(T):兼容 null
Optional<String> opt3 = Optional.ofNullable(null); // → empty
Optional<String> opt4 = Optional.ofNullable("hello"); // → of
2
3
4
5
6
7
8
9
10
源码:
public static <T> Optional<T> empty() {
@SuppressWarnings("unchecked")
Optional<T> t = (Optional<T>) EMPTY;
return t;
}
public static <T> Optional<T> of(T value) {
return new Optional<>(value); // 内部 requireNonNull
}
public static <T> Optional<T> ofNullable(T value) {
return value == null ? empty() : of(value);
}
2
3
4
5
6
7
8
9
10
11
12
13
何时用 of vs ofNullable:
// ✅ of:明确知道值非 null
return Optional.of(this.id); // id 是 final 字段且构造时校验过
// ✅ ofNullable:来自外部(DB / RPC / 用户输入)
return Optional.ofNullable(userDao.findById(id));
2
3
4
5
# 3.3 EMPTY 单例优化
所有 empty() 调用返回的是同一个对象:
Optional<String> a = Optional.empty();
Optional<Integer> b = Optional.empty();
Optional<User> c = Optional.empty();
System.out.println(a == b); // true
System.out.println(b == c); // true
// ★ 全部是同一个 EMPTY 实例(类型擦除后共用)
2
3
4
5
6
7
为什么能共用:
- 泛型在 JVM 字节码中是擦除的(
Optional<T>在运行时就是Optional) EMPTY.value == null,没有任何与T相关的运行时状态- 强转
(Optional<T>) EMPTY是安全的——因为 EMPTY 永远不会调用value上的方法
收益:
- 节省内存(避免每次
empty()创建新对象) - GC 友好(EMPTY 永驻方法区)
# 3.4 final 与 value 类
Optional 在 JDK 文档里被明确标注为 value-based class:
"This is a value-based class; programmers should treat instances that are equal as interchangeable and should not use instances for synchronization, or unpredictable behavior may occur."
Value-based class 的特征:
① final(不可继承)
② 私有构造(不能直接 new)
③ 不可变(所有字段 final)
④ equals 基于状态(不是引用相等)
⑤ 不应作为同步监视器(synchronized(optional) 是反模式!)
⑥ 不保证身份语义(identity)—— JVM 可能优化为值类型
→ 未来 Project Valhalla 的"值类型"将原生支持这种语义
2
3
4
5
6
7
8
反例:
Optional<String> opt = Optional.of("x");
synchronized (opt) { // ❌ 反模式:value-based class 不应做锁
// ...
}
2
3
4
# 4. 核心 API 全景
# 4.1 检查类方法
| 方法 | 签名 | 用途 |
|---|---|---|
isPresent | boolean | 是否有值 |
isEmpty | boolean(JDK 11+) | 是否无值 |
ifPresent | void(Consumer<T>) | 有值则消费 |
ifPresentOrElse | void(Consumer<T>, Runnable)(JDK 9+) | 有则消费/无则执行 |
Optional<User> opt = userDao.findById(id);
// ❌ 旧式判空(与传统 if (user != null) 没区别)
if (opt.isPresent()) {
User u = opt.get();
process(u);
}
// ✅ 函数式
opt.ifPresent(this::process);
// ✅ JDK 9+ 二选一
opt.ifPresentOrElse(
this::process, // 有值
() -> log.warn("user not found") // 无值
);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 4.2 转换类方法
| 方法 | 签名 | 用途 |
|---|---|---|
map | Optional<U>(Function<T,U>) | 单层映射 |
flatMap | Optional<U>(Function<T, Optional<U>>) | 嵌套展开 |
filter | Optional<T>(Predicate<T>) | 条件过滤 |
or | Optional<T>(Supplier<Optional<T>>)(JDK 9+) | 备选 Optional |
# 4.3 获取类方法
| 方法 | 签名 | 空值行为 | 推荐度 |
|---|---|---|---|
get | T | 抛 NoSuchElementException | ❌ 不推荐 |
orElse(T) | T | 返回默认值 | ✅ 默认值是常量 |
orElseGet(Supplier) | T | 调用 Supplier 获取默认值 | ✅ 默认值需要计算 |
orElseThrow() | T(JDK 10+) | 抛 NoSuchElementException | ⚠️ 等价 get |
orElseThrow(Supplier) | T | 抛自定义异常 | ✅ 业务异常 |
# 4.4 JDK 9 新增方法
JDK 9 给 Optional 加了三个实用方法:
// ① stream():转 Stream(便于配合 Stream API)
Optional<User> opt = ...;
List<User> users = opt.stream().collect(Collectors.toList());
// 有值 → 单元素流;无值 → 空流
// ② or(Supplier):返回备选 Optional
Optional<User> result = userDao.findById(id)
.or(() -> userDao.findByName(name))
.or(() -> Optional.of(DEFAULT_USER));
// ③ ifPresentOrElse:有/无两路分支
opt.ifPresentOrElse(
user -> log.info("found: {}", user),
() -> log.warn("not found")
);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
stream() 的妙用——批量过滤空 Optional:
// 场景:批量查询,过滤掉不存在的
List<User> users = userIds.stream()
.map(userDao::findById) // Stream<Optional<User>>
.flatMap(Optional::stream) // ★ 空 Optional 自动展开为空流
.collect(Collectors.toList());
// JDK 8 老写法:
List<User> users = userIds.stream()
.map(userDao::findById)
.filter(Optional::isPresent)
.map(Optional::get)
.collect(Collectors.toList());
2
3
4
5
6
7
8
9
10
11
12
# 5. 函数式链路
# 5.1 map 单层映射
map 把 Optional<T> 转换为 Optional<U>:
源码:
public <U> Optional<U> map(Function<? super T, ? extends U> mapper) {
Objects.requireNonNull(mapper);
if (!isPresent()) {
return empty(); // ★ 空则短路
} else {
return Optional.ofNullable(mapper.apply(value));
// ★ 注意:用 ofNullable,所以 mapper 返回 null 也安全
}
}
2
3
4
5
6
7
8
9
关键性质:
- ✅ 空 Optional 直接返回空(短路)
- ✅ mapper 返回 null 自动转为空 Optional(不抛 NPE)
- ✅ 类型转换:
Optional<T> → Optional<U>
Optional.of("hello")
.map(String::length) // Optional<Integer>
.map(len -> len * 2) // Optional<Integer>
.map(x -> "result: " + x); // Optional<String>
// → Optional["result: 10"]
Optional.<String>empty()
.map(String::length) // 短路:直接返回空
.map(len -> len * 2) // 短路
.map(x -> "result: " + x); // 短路
// → Optional.empty
2
3
4
5
6
7
8
9
10
11
# 5.2 flatMap 嵌套展开
问题:当 mapper 本身返回 Optional 时,用 map 会得到嵌套:
public Optional<Address> findAddress(User u) { ... }
Optional<User> userOpt = ...;
// ❌ 用 map → Optional<Optional<Address>>(嵌套)
Optional<Optional<Address>> nested = userOpt.map(this::findAddress);
// ✅ 用 flatMap → Optional<Address>(自动展平)
Optional<Address> flat = userOpt.flatMap(this::findAddress);
2
3
4
5
6
7
8
9
flatMap 源码:
public <U> Optional<U> flatMap(Function<? super T, ? extends Optional<? extends U>> mapper) {
Objects.requireNonNull(mapper);
if (!isPresent()) {
return empty();
} else {
@SuppressWarnings("unchecked")
Optional<U> r = (Optional<U>) mapper.apply(value);
return Objects.requireNonNull(r); // ★ mapper 不能返回 null
}
}
2
3
4
5
6
7
8
9
10
map vs flatMap 决策树:
mapper 的返回类型是?
├─ U(普通类型) → 用 map
└─ Optional<U> → 用 flatMap
口诀:"返回普通用 map,返回 Optional 用 flatMap"
2
3
4
5
# 5.3 filter 条件过滤
filter 在 Optional 上加一个谓词,不满足则变空:
public Optional<T> filter(Predicate<? super T> predicate) {
Objects.requireNonNull(predicate);
if (!isPresent()) {
return this;
} else {
return predicate.test(value) ? this : empty();
}
}
2
3
4
5
6
7
8
示例:
Optional<User> validAdult = Optional.ofNullable(userDao.findById(id))
.filter(User::isActive) // 必须是激活状态
.filter(u -> u.getAge() >= 18); // 必须成年
validAdult.ifPresent(this::doSomething);
2
3
4
5
# 5.4 链式重构对比
把 §1.1 的 6 层判空,对比传统写法和 Optional 写法:
传统写法(命令式):
public String getReceiverCity(Long orderId) {
Order order = orderDao.findById(orderId);
if (order == null) return "UNKNOWN";
User user = order.getUser();
if (user == null) return "UNKNOWN";
Address address = user.getAddress();
if (address == null) return "UNKNOWN";
City city = address.getCity();
if (city == null) return "UNKNOWN";
String name = city.getName();
if (name == null) return "UNKNOWN";
return name.toUpperCase();
}
// 18 行,5 个 return,5 处判空
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Optional 写法(声明式):
public String getReceiverCity(Long orderId) {
return Optional.ofNullable(orderDao.findById(orderId))
.map(Order::getUser)
.map(User::getAddress)
.map(Address::getCity)
.map(City::getName)
.map(String::toUpperCase)
.orElse("UNKNOWN");
}
// 8 行,1 个 return,无显式判空
2
3
4
5
6
7
8
9
10
对比:
| 维度 | 命令式 | Optional |
|---|---|---|
| 行数 | 18 | 8 |
| 圈复杂度 | 6 | 1 |
| 业务意图 | 被判空淹没 | 一眼看清 |
| 漏判风险 | 高 | 编译期不会漏 |
| 性能 | 略快(无对象创建) | 略慢(每层 new Optional) |
# 6. 取值方法陷阱
# 6.1 get 与 NoSuchElement
get() 是 Optional 最危险的方法——空值时抛异常,且异常信息没有上下文:
public T get() {
if (value == null) {
throw new NoSuchElementException("No value present");
}
return value;
}
2
3
4
5
6
反例:
// ❌ 没有 isPresent 检查直接 get
String name = userDao.findById(id).get().getName();
// 如果 id 不存在 → NoSuchElementException
// 与原生 NPE 没有本质区别,反而堆栈更深,定位更难
// ❌ 先 isPresent 再 get(与 if (user != null) 没区别)
if (opt.isPresent()) {
process(opt.get());
}
// ✅ 用 ifPresent
opt.ifPresent(this::process);
// ✅ 用 orElse / orElseGet / orElseThrow
String name = opt.map(User::getName).orElse("Unknown");
2
3
4
5
6
7
8
9
10
11
12
13
14
15
JDK 10 加入 orElseThrow() 无参版本,等价于 get(),但语义更清晰:
// JDK 10+ 推荐替代 get
String name = opt.orElseThrow().getName();
// 抛 NoSuchElementException,但读起来"我明确接受这种风险"
2
3
# 6.2 orElse 求值时机
orElse 的默认值永远会被求值(即使 Optional 不为空):
public T orElse(T other) {
return value != null ? value : other;
// ★ other 在调用 orElse 之前就已经被求值了
}
2
3
4
陷阱:
public User createDefaultUser() {
log.info("creating default user"); // ★ 副作用
return new User("default"); // ★ 创建对象
}
// ❌ 即使 opt 有值,createDefaultUser() 也会被调用
User user = opt.orElse(createDefaultUser());
// 1. 先执行 createDefaultUser()(副作用日志 + 对象创建)
// 2. 再判断 opt 是否为空
// 3. 如果 opt 有值,刚创建的 default user 被丢弃
2
3
4
5
6
7
8
9
10
JMH 测试(Optional 90% 有值场景):
Benchmark Mode Cnt Score Error Units
orElse(createDefaultUser()) avgt 10 85.234 ± 1.234 ns/op
orElseGet(this::createDefaultUser) avgt 10 12.847 ± 0.345 ns/op
orElse 慢 7 倍:因为每次都执行 createDefaultUser
2
3
4
5
# 6.3 orElseGet 惰性求值
orElseGet 接收 Supplier,只在 Optional 为空时才调用:
public T orElseGet(Supplier<? extends T> supplier) {
return value != null ? value : supplier.get();
// ★ 仅当 value == null 时才调用 supplier.get()
}
2
3
4
正确用法:
// ✅ 默认值需要计算 → orElseGet
User user = opt.orElseGet(this::createDefaultUser);
// ✅ 默认值是常量 → orElse(更简洁)
String name = opt.orElse("Unknown");
String name = opt.orElse(DEFAULT_NAME); // DEFAULT_NAME 是 final 常量
2
3
4
5
6
决策:
默认值的成本?
├─ 零成本(字面量、常量、已有变量) → orElse
└─ 有成本(new 对象、查 DB、调 RPC) → orElseGet
2
3
# 6.4 orElseThrow 异常控制
业务逻辑中"找不到就抛异常"是常见需求:
// ❌ 旧式
User user = opt.orElseThrow(() -> new BusinessException("用户不存在: " + id));
// ✅ JDK 10+ 简化(无参版本,但只能抛 NoSuchElementException)
User user = opt.orElseThrow();
// ✅ 业务异常推荐写法
User user = userDao.findById(id)
.orElseThrow(() -> new ResourceNotFoundException("user", id));
2
3
4
5
6
7
8
9
异常类型选择:
| 场景 | 异常类型 |
|---|---|
| API 返回 404 | ResourceNotFoundException(业务异常) |
| 不应发生的"理论上不可能空" | IllegalStateException |
| 参数缺失 | IllegalArgumentException |
| 调试断言 | NoSuchElementException(默认) |
# 7. 使用边界规约
JDK Stream 库的设计者 Brian Goetz 在 Stack Overflow 上明确说:
"Optional was designed to provide a limited mechanism for library method return types where there needed to be a clear way to represent 'no result.' Using it for something else (like an Optional-typed field, or a method parameter) is a misuse."
# 7.1 适合做返回值
✅ 核心场景:方法返回值(特别是查询接口)
// ✅ DAO 查询
Optional<User> findById(Long id);
// ✅ 流式处理
Optional<Order> findFirst(Predicate<Order> p);
// ✅ Map.get 的现代替代
Optional<String> get(String key) {
return Optional.ofNullable(map.get(key));
}
// ✅ 配置项
Optional<String> getProperty(String key);
2
3
4
5
6
7
8
9
10
11
12
13
为什么这是 Optional 的甜点:
- 调用方必须面对"可能不存在"
- 编译器不允许调用方"假装值一定存在"
- 语义清晰:返回
Optional<User>≠ 可能返回 null - 配合 Stream API 风格统一
# 7.2 不要做参数
❌ 不要把 Optional 作为方法参数
// ❌ 反模式
public List<Order> queryOrders(Optional<Long> userId, Optional<Status> status) { ... }
// 调用方:
queryOrders(Optional.of(123L), Optional.empty());
queryOrders(Optional.ofNullable(maybeUserId), Optional.empty());
// 比 null 还啰嗦!
2
3
4
5
6
7
原因:
- 调用方仍然要"自己包装"
- 永远存在
optional == null的可能(!) - 无法解决"哪些参数是必填、哪些是选填"
- 应该用方法重载、Builder 模式或
@Nullable注解
正确替代:
// ✅ 方案 1:方法重载
public List<Order> queryOrders(Long userId) { ... }
public List<Order> queryOrders(Long userId, Status status) { ... }
// ✅ 方案 2:Builder
QueryBuilder.of()
.userId(123L)
.status(Status.PAID) // 可选
.build();
// ✅ 方案 3:@Nullable 注解(IDE 静态检查)
public List<Order> queryOrders(Long userId, @Nullable Status status) { ... }
2
3
4
5
6
7
8
9
10
11
12
# 7.3 不要做字段
❌ 不要把 Optional 作为字段
// ❌ 反模式
public class User {
private Long id;
private Optional<String> nickname;
private Optional<Address> address;
}
2
3
4
5
6
三大原因:
原因 1:不能序列化
public class User implements Serializable {
private Optional<String> nickname; // ★ Optional 不实现 Serializable
}
// 序列化时抛 NotSerializableException: java.util.Optional
2
3
4
原因 2:内存浪费
普通字段:
private String nickname; // 1 个引用(4/8 字节)
Optional 字段:
private Optional<String> nickname;
// 1 个 Optional 对象(16+ 字节对象头)
// + 内部 String 引用
// → 多 2~3 倍内存
2
3
4
5
6
7
8
原因 3:JPA / Jackson / 反射不友好
// JPA 不知道怎么映射 Optional<Address>
@Entity
public class User {
@OneToOne
private Optional<Address> address; // ❌ 报错或被错误处理
}
// Jackson 序列化 Optional 默认行为也很怪
{
"nickname": {"present": true} // ❌ 不是想要的结果
}
2
3
4
5
6
7
8
9
10
11
正确做法:字段就用普通类型 + getter 返回 Optional
public class User {
private String nickname; // ★ 字段普通类型,可以是 null
public Optional<String> getNickname() { // ★ getter 返回 Optional
return Optional.ofNullable(nickname);
}
}
2
3
4
5
6
7
# 7.4 不要做集合元素
❌ 不要把 Optional 作为集合元素
// ❌ 反模式
List<Optional<User>> users;
Map<String, Optional<Order>> map;
2
3
原因:
- 集合本身的"空"已经表达了"没值"——再套 Optional 是冗余
- 调用方还要再 isPresent 一遍
Map.get返回 null 的语义已经被广泛接受
正确做法:
// ✅ List:直接过滤掉空值
List<User> users = userIds.stream()
.map(userDao::findById) // Stream<Optional<User>>
.flatMap(Optional::stream) // ★ 空的自动跳过
.collect(Collectors.toList());
// ✅ Map:用 getOrDefault 或 computeIfAbsent
String value = map.getOrDefault(key, "default");
String value = Optional.ofNullable(map.get(key)).orElse("default");
2
3
4
5
6
7
8
9
# 8. 设计权衡之谜
# 8.1 为何不可序列化
问题:为什么 Optional 不实现 Serializable?
Brian Goetz 的官方解释(OpenJDK 邮件列表):
"Optional was specifically NOT designed to be serializable. It's intended as a return type, and adding Serializable would have invited people to use it in places it wasn't designed for (like fields)."
深层原因:
原因 ①:刻意限制使用边界
不实现 Serializable → 不能做字段(DTO/Entity 通常 Serializable)
→ 引导用户只在返回值用 Optional
原因 ②:版本兼容噩梦
Serializable 类的字段一旦确定就难以改动
Optional 未来可能演进为值类型(Project Valhalla)
实现 Serializable 会锁死内部表示
原因 ③:value-based class 语义
序列化破坏 value 语义(反序列化产生新对象)
2
3
4
5
6
7
8
9
10
11
# 8.2 为何不能继承
Optional 是 final 的,理由:
public final class Optional<T> { ... }
原因:
① value-based class 要求"两个 equal 的实例可互换"
继承会引入子类,子类可能添加状态 → 破坏互换性
② 未来值类型迁移
Valhalla 的值类型不允许继承
现在留下继承可能性 → 未来无法迁移
③ 设计简单性
Optional 不需要扩展点,行为完全确定
2
3
4
5
6
7
8
9
# 8.3 为何无 isPresent setter
Optional 不可变——没有 setValue / clear 之类的方法:
不可变的好处:
✅ 线程安全(无状态变更)
✅ 适合做值传递(无需防御性拷贝)
✅ 适合并发场景(Stream / 并行流)
✅ 缓存安全(hashCode 稳定)
✅ EMPTY 单例可复用
2
3
4
5
6
如果允许可变,EMPTY 单例就完蛋了——多线程下随时可能被改成 present。
# 8.4 为何 equals 特殊
Optional.equals 实现:
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (!(obj instanceof Optional)) return false;
Optional<?> other = (Optional<?>) obj;
return Objects.equals(value, other.value); // ★ 基于 value
}
2
3
4
5
6
7
关键性质:
Optional<String> a = Optional.of("hello");
Optional<String> b = Optional.of("hello");
Optional<String> c = Optional.empty();
Optional<String> d = Optional.empty();
a.equals(b); // true ← value 相等
c.equals(d); // true ← 都是空
a == b; // false ← 引用不同
c == d; // true ← EMPTY 单例
2
3
4
5
6
7
8
9
注意事项:
// ⚠️ 不要在 HashMap key 中用 Optional
Map<Optional<String>, Integer> map = new HashMap<>();
map.put(Optional.of("a"), 1);
// 能编译,但语义古怪 + 性能差 + 反模式
// → 直接用 String 当 key 就好
2
3
4
5
# 9. 实战重构套路
# 9.1 替代多层判空
模式 1:链式属性访问
// before
String city = null;
if (order != null && order.getUser() != null && order.getUser().getAddress() != null) {
city = order.getUser().getAddress().getCity();
}
// after
String city = Optional.ofNullable(order)
.map(Order::getUser)
.map(User::getAddress)
.map(Address::getCity)
.orElse(null);
2
3
4
5
6
7
8
9
10
11
12
模式 2:双默认值
// before
String name = config.get("name");
if (name == null || name.isEmpty()) {
name = "anonymous";
}
// after
String name = Optional.ofNullable(config.get("name"))
.filter(s -> !s.isEmpty())
.orElse("anonymous");
2
3
4
5
6
7
8
9
10
模式 3:先验证再处理
// before
User user = userDao.findById(id);
if (user == null) {
throw new ResourceNotFoundException("user", id);
}
if (!user.isActive()) {
throw new BusinessException("user inactive");
}
return user.getName();
// after
return userDao.findById(id)
.filter(User::isActive)
.map(User::getName)
.orElseThrow(() -> new BusinessException("user not found or inactive"));
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 9.2 配合 Stream 使用
场景 1:批量查询,过滤不存在的
List<User> users = userIds.stream()
.map(userDao::findById) // Stream<Optional<User>>
.flatMap(Optional::stream) // ★ JDK 9+ 黑魔法
.collect(Collectors.toList());
2
3
4
场景 2:找第一个匹配项
Optional<Order> firstPaid = orders.stream()
.filter(o -> o.getStatus() == PAID)
.findFirst();
firstPaid.ifPresent(this::process);
2
3
4
5
场景 3:归约后转 Optional
Optional<BigDecimal> totalAmount = orders.stream()
.map(Order::getAmount)
.reduce(BigDecimal::add); // ★ reduce 单参数版返回 Optional
BigDecimal sum = totalAmount.orElse(BigDecimal.ZERO);
2
3
4
5
# 9.3 DAO 返回值规范
单条查询统一返回 Optional:
public interface UserRepository {
// ✅ 单条查询:Optional
Optional<User> findById(Long id);
Optional<User> findByEmail(String email);
// ✅ 批量查询:List(空集合表示无结果,不要用 Optional<List>!)
List<User> findByStatus(Status status);
// ✅ 计数:原始类型(不要用 Optional<Long>)
long countByStatus(Status status);
// ✅ 存在性:boolean
boolean existsByEmail(String email);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
反模式:
// ❌ 不要 Optional 包集合
Optional<List<User>> findAll(); // 永远是 of(emptyList),无意义
// ❌ 不要 Optional 包基本类型
Optional<Long> count(); // 应该用 long 或 OptionalLong
2
3
4
5
# 9.4 反模式清单
| 反模式 | 错误 | 正确 |
|---|---|---|
Optional<List<X>> | 包集合 | List<X>(空集合就好) |
Optional<Optional<X>> | 嵌套 | 用 flatMap 展平 |
if(opt.isPresent()) opt.get() | 等价 if-null | opt.ifPresent(...) |
opt.get() 直接调 | 等价 NPE | orElseThrow / orElse |
orElse(new Heavy()) | 永远求值 | orElseGet(() -> new Heavy()) |
| Optional 当字段 | 不可序列化 | 普通字段 + getter 返回 Optional |
| Optional 当参数 | 调用方啰嗦 | 重载 / Builder / @Nullable |
| Optional 包基本类型 | 装箱开销 | OptionalInt/Long/Double |
synchronized(opt) | value-based 反模式 | 用其他对象做锁 |
| Optional 做集合元素 | 冗余 | 直接过滤空值 |
# 10. 综合案例串讲
# 10.1 双案例真相揭晓
① §1.1 6 层判空 通过 §5.4 的链式重构降到 8 行——这就是 Optional 的"甜点"场景:返回值链路 + 多层属性访问。
② §1.2 反向滥用 五大反模式根因表:
| 反例 | 根因 | 章节 |
|---|---|---|
| Optional 当字段 | 不实现 Serializable + 内存浪费 + JPA 不友好 | §7.3、§8.1 |
| Optional 当参数 | 调用方仍要包装 + 无法解决可选性问题 | §7.2 |
| 集合元素 Optional | 集合的"空"已表达"没值",冗余 | §7.4 |
| Optional 嵌套 | 应该用 flatMap 展平 | §5.2 |
| 用了又 get | 等价于 NPE,且堆栈更深 | §6.1 |
③ 5 大追问全部作答:
| 追问 | 答案 | 章节 |
|---|---|---|
| ① NPE 为何诞生 | Tony Hoare 1965 引入;半个世纪十亿美元代价 | §2.1 |
| ② Optional 内部 | final value 字段 + EMPTY 单例 + value-based class | §3 |
| ③ map/filter 短路 | 内部 isPresent 判断,空则直接返回 empty | §5.1 |
| ④ orElse vs orElseGet | orElse 永远求值,orElseGet 惰性求值(差 7 倍) | §6.2、§6.3 |
| ⑤ 反模式根因 | 受限于库级方案 + 边界靠规约弥补 | §2.4、§7、§8 |
# 10.2 一次调用的旅行
把 userDao.findById(id).map(User::getName).orElse("Unknown") 串成完整生命线:
T 0 userDao.findById(id)
[§4] DAO 层执行 SELECT * FROM user WHERE id = ?
├─ 查到 → return Optional.of(user)
│ [§3.2] new Optional<>(user),value = user
└─ 未查到 → return Optional.empty()
[§3.3] 复用全局 EMPTY 单例
T+1ns .map(User::getName)
[§5.1] map 内部:
if (!isPresent()) return empty(); ← 短路
else return Optional.ofNullable(mapper.apply(value));
[§5.1] mapper 是 User::getName 方法引用
├─ value 非空 → 调用 user.getName()
│ ├─ 返回 "张三" → Optional.of("张三")
│ └─ 返回 null → Optional.empty()
└─ value 为空 → 直接返回 EMPTY(不调用 mapper)
T+2ns .orElse("Unknown")
[§6.2] orElse 实现:
return value != null ? value : "Unknown";
[§6.2] "Unknown" 是字符串字面量
├─ 进入字符串常量池,零成本
└─ 即使 Optional 有值,"Unknown" 也"已经在那里"
→ 这就是为什么常量字面量用 orElse 没问题
T+3ns 返回最终 String
[§3.1] Optional 对象生命周期结束
├─ 有值的 Optional → 等待 GC(短期对象,新生代回收)
└─ EMPTY 单例 → 永驻方法区
跨篇引用全景:
[04] HashMap ← Optional 不应做 HashMap key(§8.4)
[13] 字节码 ← Optional 是常规类,无字节码特殊处理
[16] OOM 现场 ← Optional 字段化导致内存浪费(§7.3)
[27] Lambda ← Optional 的 ifPresent/map/filter 全靠 Lambda
[28] Stream ← Optional.stream() 转 Stream(§4.4)
[40] CompletableFuture ← Optional 与 CompletableFuture 的容器思维一脉相承
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# 10.3 设计哲学回扣
跳出 API 细节,提炼贯穿 Optional 设计的三条工程哲学:
库级方案的边界即规约:Java 选择"库级 Optional"而非"语法级可空类型",是历史包袱与渐进演进的折中。这种折中留下了使用上的灰色地带——
Optional本身可以是null、可以做字段、可以做参数(编译器不会拦截)。设计者只能通过明确 API 边界(不实现 Serializable、final class、value-based class)+ 文档强约束来引导正确使用。这告诉我们一件事:当语法层面无法保证时,规约就是最后的防线——无论是团队 Code Review、IDE 静态检查(IntelliJ 对 Optional 字段会警告)、还是 SpotBugs 规则,都是在弥补语法的不足。"显式可空性"是类型契约的进化方向:
Optional<User>这个类型签名本身就是一份契约——告诉调用方"这里可能没值,请你处理"。从 ALGOL 的 null、到 Haskell 的 Maybe、到 Rust 的Option<T>、到 Kotlin 的String?、再到 Java 的Optional,整个程序语言史都在朝同一个方向演进——把"可能不存在"从隐含语义升级为显式类型。这与第 26 篇的"注解显式化元数据"、第 28 篇的"Stream 显式化数据流"是同一种哲学——让重要的设计意图变成类型系统的一等公民。"值容器"思想的家族延续:
Optional<T>、Stream<T>、CompletableFuture<T>、Mono<T>(Reactor)共享同一个抽象——把值(或值的缺失/异步/流)包装成容器,提供 map/flatMap/filter 等组合子。这背后是 Monad(单子)的范畴论思想——用统一的容器抽象表达"值 + 上下文"(Optional 是"值或无",Stream 是"值的序列",CompletableFuture 是"值或未来")。掌握 Optional 的 map/flatMap,就掌握了进入 Reactive、Functional 编程范式的钥匙——API 形态完全相同,只是上下文不同。
# 10.4 Optional 速查表
创建方法速查:
Optional.empty() 永远返回 EMPTY 单例
Optional.of(value) value 不能为 null(否则 NPE)
Optional.ofNullable(value) 兼容 null(推荐外部数据用此)
2
3
取值方法速查:
get() 空则抛 NoSuchElementException(不推荐)
orElse(T) 空则返回常量(默认值是字面量/常量时用)
orElseGet(Supplier) 空则计算(默认值有成本时用)
orElseThrow() JDK 10+,等价 get
orElseThrow(Supplier) 空则抛自定义异常(业务异常用)
2
3
4
5
检查方法速查:
isPresent() 有值 true
isEmpty() JDK 11+,无值 true
ifPresent(Consumer) 有值则消费
ifPresentOrElse(Cons, Run) JDK 9+,有值消费 / 无值执行
2
3
4
转换方法速查:
map(Function<T,U>) T → U,自动处理 mapper 返回 null
flatMap(Function<T,Optional<U>>) T → Optional<U>,避免嵌套
filter(Predicate) 不满足则变空
or(Supplier<Optional>) JDK 9+,备选 Optional
stream() JDK 9+,转 Stream(批量过滤神器)
2
3
4
5
使用边界铁律:
铁律 1:Optional 只做返回值(库设计原则)
铁律 2:永远不要 Optional 做参数(用重载/Builder/@Nullable)
铁律 3:永远不要 Optional 做字段(不可序列化 + JPA 不友好)
铁律 4:永远不要 Optional 做集合元素(冗余 + 性能差)
铁律 5:永远不要 Optional 嵌套(用 flatMap 展平)
铁律 6:永远不要 Optional<List<X>>(直接 List<X>,空集合表达"无")
铁律 7:基本类型用 OptionalInt/Long/Double,避免装箱
铁律 8:默认值是常量用 orElse,需要计算用 orElseGet
铁律 9:业务异常用 orElseThrow(Supplier),不要 get 后判断
铁律 10:永远不要 synchronized(optional)(value-based class)
2
3
4
5
6
7
8
9
10
何时用 Optional 决策树:
是否在写方法的返回值?
├─ 否 → ❌ 不要用 Optional
└─ 是 → 是否可能"没有结果"?
├─ 否(一定有值)→ ❌ 直接返回 T
├─ 集合空? → ❌ 返回空集合
├─ 数值 0? → ❌ 返回 0
└─ 真的"无" → ✅ 返回 Optional<T>
2
3
4
5
6
7
至此第 29 篇完成——我们用 Tony Hoare 自白溯源、Optional 类源码剖析、map/flatMap/filter 短路实现、orElse vs orElseGet 7 倍性能差、四大使用边界规约(返回值 ✅ / 参数 ❌ / 字段 ❌ / 集合 ❌)、不可序列化背后的设计意图,把"Optional 不只是 NPE 检查器,而是显式可空性的类型契约"完整还原。
下一篇是卷三第 30 篇:Record / Sealed / Pattern Matching 现代 Java 类型系统——卷三收官篇 🏁,把 Java 14~21 引入的三大语言特性(Record 不可变数据载体、Sealed 密封继承、Pattern Matching 模式匹配)一次讲透,揭开"代数数据类型(ADT)"在 Java 中的完整落地,以及它如何重塑 DDD 与函数式编程的边界。