编程进阶网 编程进阶网
首页
  • 计算机原理
  • 操作系统
  • 网络协议
  • 数据库原理
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 性能优化
  • 编程原理
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 基础认知
  • 线性结构
  • 树与哈希
  • 工业级实现
  • 算法思想
  • 实战与综合
  • 算法题考核
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++开发技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript高级
  • Android库解读
  • Android专栏
  • Android智能硬件
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Linux实践开发
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接

杨充

专注编程 · 终身学习者
首页
  • 计算机原理
  • 操作系统
  • 网络协议
  • 数据库原理
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 性能优化
  • 编程原理
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 基础认知
  • 线性结构
  • 树与哈希
  • 工业级实现
  • 算法思想
  • 实战与综合
  • 算法题考核
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++开发技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript高级
  • Android库解读
  • Android专栏
  • Android智能硬件
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Linux实践开发
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接
  • README
  • C语言入门精通

  • Cpp入门到精通

  • Java入门精通

    • README
    • 入门教程

    • 综合案例

    • 专栏博客

      • README
      • JVM内存模型与对象
      • 类加载与双亲委派
      • 垃圾回收与GC调优
      • 异常体系与JVM机制
      • 字节码指令集javap实战
      • JIT编译与去优化机制
      • JVM性能诊断工具链
      • OOM八大现场全景剖析
      • JVM参数调优全景图
      • GraalVM与AOT编译原理
      • HashMap底层哈希设计
      • String不可变与常量池
      • ArrayList与LinkedList源码
      • ConcurrentHashMap并发
      • TreeMap与红黑树原理
      • LinkedHashMap与LRU实现
      • Java数字类型原理
      • Object通用方法的契约
      • 泛型擦除与类型系统
      • 枚举原理与最佳实践
      • 注解原理与编译期处理
      • Lambda与引用底层原理
      • Stream原理与流水线设计
      • Optional设计原理
        • 1. 案例引入
          • 1.1 千层嵌套的判空
          • 1.2 Optional 的反向滥用
          • 1.3 我们要回答什么
        • 2. 十亿美元错误
          • 2.1 Tony Hoare 自白
          • 2.2 NPE 的传染性
          • 2.3 各语言的解法
          • 2.4 Java 的折中方案
        • 3. Optional 类剖析
          • 3.1 类与字段结构
          • 3.2 三种创建方式
          • 3.3 EMPTY 单例优化
          • 3.4 final 与 value 类
        • 4. 核心 API 全景
          • 4.1 检查类方法
          • 4.2 转换类方法
          • 4.3 获取类方法
          • 4.4 JDK 9 新增方法
        • 5. 函数式链路
          • 5.1 map 单层映射
          • 5.2 flatMap 嵌套展开
          • 5.3 filter 条件过滤
          • 5.4 链式重构对比
        • 6. 取值方法陷阱
          • 6.1 get 与 NoSuchElement
          • 6.2 orElse 求值时机
          • 6.3 orElseGet 惰性求值
          • 6.4 orElseThrow 异常控制
        • 7. 使用边界规约
          • 7.1 适合做返回值
          • 7.2 不要做参数
          • 7.3 不要做字段
          • 7.4 不要做集合元素
        • 8. 设计权衡之谜
          • 8.1 为何不可序列化
          • 8.2 为何不能继承
          • 8.3 为何无 isPresent setter
          • 8.4 为何 equals 特殊
        • 9. 实战重构套路
          • 9.1 替代多层判空
          • 9.2 配合 Stream 使用
          • 9.3 DAO 返回值规范
          • 9.4 反模式清单
        • 10. 综合案例串讲
          • 10.1 双案例真相揭晓
          • 10.2 一次调用的旅行
          • 10.3 设计哲学回扣
          • 10.4 Optional 速查表
      • Record密封类与模式
      • 反射机制与动态代理
      • MethodHandle与VarHandle
      • 三大字节码框架对比
      • JavaAgent与Instrumentation机制
      • AOP三种实现路线对比
      • synchronized与锁升级
      • volatile与JMM内存模型
      • 线程池核心源码设计
      • Thread线程生命周期
      • AQS同步框架源码
      • 并发锁三剑客
      • CAS和Atomic深入分析
      • 五大同步器对比
      • CompletableFuture异步
      • IO模型演进BIO到AIO
      • ByteBuffer与堆外内存
      • 序列化原理与替代方案
      • 文件IO与NIO.2
      • 面向对象的真意
      • JDK设计模式上
      • JDK设计模式下
      • SPI与模块化设计
  • Go入门到精通

  • JavaScript入门

  • CodeX
  • Java入门精通
  • 专栏博客
杨充
2026-06-02
目录

Optional设计原理

# 24.Optional设计原理

# 目录介绍

  • 1. 案例引入
    • 1.1 千层嵌套的判空
    • 1.2 Optional 的反向滥用
    • 1.3 我们要回答什么
  • 2. 十亿美元错误
    • 2.1 Tony Hoare 自白
    • 2.2 NPE 的传染性
    • 2.3 各语言的解法
    • 2.4 Java 的折中方案
  • 3. Optional 类剖析
    • 3.1 类与字段结构
    • 3.2 三种创建方式
    • 3.3 EMPTY 单例优化
    • 3.4 final 与 value 类
  • 4. 核心 API 全景
    • 4.1 检查类方法
    • 4.2 转换类方法
    • 4.3 获取类方法
    • 4.4 JDK 9 新增方法
  • 5. 函数式链路
    • 5.1 map 单层映射
    • 5.2 flatMap 嵌套展开
    • 5.3 filter 条件过滤
    • 5.4 链式重构对比
  • 6. 取值方法陷阱
    • 6.1 get 与 NoSuchElement
    • 6.2 orElse 求值时机
    • 6.3 orElseGet 惰性求值
    • 6.4 orElseThrow 异常控制
  • 7. 使用边界规约
    • 7.1 适合做返回值
    • 7.2 不要做参数
    • 7.3 不要做字段
    • 7.4 不要做集合元素
  • 8. 设计权衡之谜
    • 8.1 为何不可序列化
    • 8.2 为何不能继承
    • 8.3 为何无 isPresent setter
    • 8.4 为何 equals 特殊
  • 9. 实战重构套路
    • 9.1 替代多层判空
    • 9.2 配合 Stream 使用
    • 9.3 DAO 返回值规范
    • 9.4 反模式清单
  • 10. 综合案例串讲
    • 10.1 双案例真相揭晓
    • 10.2 一次调用的旅行
    • 10.3 设计哲学回扣
    • 10.4 Optional 速查表

# 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";
    }
}
1
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");
}
1
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,且更难定位
1
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章
1
2
3
4
5

# 1.3 我们要回答什么

第 29 篇要把"Optional 是什么、为什么这么设计、什么时候用什么时候不用"讲透——Optional 不是"NPE 检查器",而是一种"显式可空性"的类型契约:

Optional 的两层定位:

定位 ①:API 返回值的"可能不存在"契约
  返回 Optional<User> ≠ 返回 User 也可能 null
  Optional 强制调用方面对"不存在"这个状态
  → 把"可能为空"从隐含假设升级为类型签名

定位 ②:函数式链路的"短路容器"
  容器中有值 → map/filter/flatMap 正常处理
  容器为空 → 整条链短路,直接到 orElse
  → 与 Stream 的设计哲学一脉相承
1
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章)
1
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?必须看注释或源码
1
2
3
4
5
6

NPE 的三大特点:

特点 ①:传染性
  null 一旦产生,会在调用链中传递
  user.getAddress().getCity() 任意一层为 null 都会爆

特点 ②:延迟性  
  null 产生的位置和爆炸的位置往往很远
  Service 返回 null → Controller 调用 → JSON 序列化报错

特点 ③:不可区分性
  返回 null 的语义不明确:
    是"没找到"?
    是"出错了"?
    是"还没加载"?
    是"正常的空值"?
  调用方无法区分
1
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 操作符,提供默认值
1
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 单独存在)
1
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
    }
    
    // ... 方法略
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

关键观察:

  1. final class 不允许继承(§8.2 解释)
  2. 没有 setter,value 用 final 修饰(不可变)
  3. 不实现 Serializable(§8.1 解释)
  4. 用 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
1
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);
}
1
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));
1
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 实例(类型擦除后共用)
1
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 的"值类型"将原生支持这种语义
1
2
3
4
5
6
7
8

反例:

Optional<String> opt = Optional.of("x");
synchronized (opt) {        // ❌ 反模式:value-based class 不应做锁
    // ...
}
1
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")      // 无值
);
1
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")
);
1
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());
1
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 也安全
    }
}
1
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
1
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);
1
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
    }
}
1
2
3
4
5
6
7
8
9
10

map vs flatMap 决策树:

mapper 的返回类型是?
├─ U(普通类型)   → 用 map
└─ Optional<U>     → 用 flatMap

口诀:"返回普通用 map,返回 Optional 用 flatMap"
1
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();
    }
}
1
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);
1
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 处判空
1
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,无显式判空
1
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;
}
1
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");
1
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,但读起来"我明确接受这种风险"
1
2
3

# 6.2 orElse 求值时机

orElse 的默认值永远会被求值(即使 Optional 不为空):

public T orElse(T other) {
    return value != null ? value : other;
    // ★ other 在调用 orElse 之前就已经被求值了
}
1
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 被丢弃
1
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
1
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()
}
1
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 常量
1
2
3
4
5
6

决策:

默认值的成本?
├─ 零成本(字面量、常量、已有变量)  → orElse
└─ 有成本(new 对象、查 DB、调 RPC)  → orElseGet
1
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));
1
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);
1
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 还啰嗦!
1
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) { ... }
1
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;
}
1
2
3
4
5
6

三大原因:

原因 1:不能序列化

public class User implements Serializable {
    private Optional<String> nickname;    // ★ Optional 不实现 Serializable
}
// 序列化时抛 NotSerializableException: java.util.Optional
1
2
3
4

原因 2:内存浪费

普通字段:
  private String nickname;       // 1 个引用(4/8 字节)
  
Optional 字段:
  private Optional<String> nickname;
  // 1 个 Optional 对象(16+ 字节对象头)
  // + 内部 String 引用
  // → 多 2~3 倍内存
1
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}    // ❌ 不是想要的结果
}
1
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);
    }
}
1
2
3
4
5
6
7

# 7.4 不要做集合元素

❌ 不要把 Optional 作为集合元素

// ❌ 反模式
List<Optional<User>> users;
Map<String, Optional<Order>> map;
1
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");
1
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 语义(反序列化产生新对象)
1
2
3
4
5
6
7
8
9
10
11

# 8.2 为何不能继承

Optional 是 final 的,理由:

public final class Optional<T> { ... }
1

原因:

① value-based class 要求"两个 equal 的实例可互换"
   继承会引入子类,子类可能添加状态 → 破坏互换性

② 未来值类型迁移
   Valhalla 的值类型不允许继承
   现在留下继承可能性 → 未来无法迁移

③ 设计简单性
   Optional 不需要扩展点,行为完全确定
1
2
3
4
5
6
7
8
9

# 8.3 为何无 isPresent setter

Optional 不可变——没有 setValue / clear 之类的方法:

不可变的好处:
✅ 线程安全(无状态变更)
✅ 适合做值传递(无需防御性拷贝)
✅ 适合并发场景(Stream / 并行流)
✅ 缓存安全(hashCode 稳定)
✅ EMPTY 单例可复用
1
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
}
1
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 单例
1
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 就好
1
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);
1
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");
1
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"));
1
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());
1
2
3
4

场景 2:找第一个匹配项

Optional<Order> firstPaid = orders.stream()
    .filter(o -> o.getStatus() == PAID)
    .findFirst();

firstPaid.ifPresent(this::process);
1
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);
1
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);
}
1
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
1
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 的容器思维一脉相承
1
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 设计的三条工程哲学:

  1. 库级方案的边界即规约:Java 选择"库级 Optional"而非"语法级可空类型",是历史包袱与渐进演进的折中。这种折中留下了使用上的灰色地带——Optional 本身可以是 null、可以做字段、可以做参数(编译器不会拦截)。设计者只能通过明确 API 边界(不实现 Serializable、final class、value-based class)+ 文档强约束来引导正确使用。这告诉我们一件事:当语法层面无法保证时,规约就是最后的防线——无论是团队 Code Review、IDE 静态检查(IntelliJ 对 Optional 字段会警告)、还是 SpotBugs 规则,都是在弥补语法的不足。

  2. "显式可空性"是类型契约的进化方向:Optional<User> 这个类型签名本身就是一份契约——告诉调用方"这里可能没值,请你处理"。从 ALGOL 的 null、到 Haskell 的 Maybe、到 Rust 的 Option<T>、到 Kotlin 的 String?、再到 Java 的 Optional,整个程序语言史都在朝同一个方向演进——把"可能不存在"从隐含语义升级为显式类型。这与第 26 篇的"注解显式化元数据"、第 28 篇的"Stream 显式化数据流"是同一种哲学——让重要的设计意图变成类型系统的一等公民。

  3. "值容器"思想的家族延续: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(推荐外部数据用此)
1
2
3

取值方法速查:

get()                         空则抛 NoSuchElementException(不推荐)
orElse(T)                     空则返回常量(默认值是字面量/常量时用)
orElseGet(Supplier)           空则计算(默认值有成本时用)
orElseThrow()                 JDK 10+,等价 get
orElseThrow(Supplier)         空则抛自定义异常(业务异常用)
1
2
3
4
5

检查方法速查:

isPresent()                   有值 true
isEmpty()                     JDK 11+,无值 true
ifPresent(Consumer)           有值则消费
ifPresentOrElse(Cons, Run)    JDK 9+,有值消费 / 无值执行
1
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(批量过滤神器)
1
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)
1
2
3
4
5
6
7
8
9
10

何时用 Optional 决策树:

是否在写方法的返回值?
├─ 否 → ❌ 不要用 Optional
└─ 是 → 是否可能"没有结果"?
        ├─ 否(一定有值)→ ❌ 直接返回 T
        ├─ 集合空?     → ❌ 返回空集合
        ├─ 数值 0?     → ❌ 返回 0
        └─ 真的"无"     → ✅ 返回 Optional<T>
1
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 与函数式编程的边界。

上次更新: 2026/06/10, 11:13:41
Stream原理与流水线设计
Record密封类与模式

← Stream原理与流水线设计 Record密封类与模式→

最近更新
01
信号崩溃快速排查
06-15
02
CoreDump破案
06-15
03
perf火焰图实战
06-15
更多文章>
Theme by Vdoing | Copyright © 2019-2026 杨充 | MIT License | 桂ICP备2024034950号 | 桂公网安备45142202000030
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式