Optional与空值处理
Optional 是 Java 8 引入的容器类,用于显式表达“一个值可能存在,也可能不存在”。它的目标不是消灭 null,而是让返回值的空语义更清晰。
# 1. 为什么需要 Optional
传统写法:
User user = repository.findById(id);
if (user != null) {
return user.getName();
}
return "unknown";
Optional 写法:
String name = repository.findById(id)
.map(User::getName)
.orElse("unknown");
方法签名:
Optional<User> findById(Long id);
调用方一眼能看出结果可能不存在。
# 2. 创建 Optional
Optional<User> empty = Optional.empty();
Optional<User> user = Optional.of(new User());
Optional<User> nullable = Optional.ofNullable(maybeNull);
区别:
| 方法 | null 行为 |
|---|---|
empty | 创建空 Optional |
of | 参数为 null 时抛 NPE |
ofNullable | 参数为 null 时返回 empty |
如果值可能为 null,使用 ofNullable。
# 3. 获取值
不推荐:
User user = optional.get();
如果为空会抛 NoSuchElementException。
推荐:
User user = optional.orElse(defaultUser);
或:
User user = optional.orElseThrow(() -> new BusinessException("USER_NOT_FOUND"));
# 4. orElse 与 orElseGet
User user = optional.orElse(createDefaultUser());
orElse 的参数会提前计算。
User user = optional.orElseGet(() -> createDefaultUser());
orElseGet 只有为空时才执行 Supplier。
流程:
Optional 有值
├─ orElse:默认值已提前创建
└─ orElseGet:不执行 Supplier
默认值创建成本高时,优先 orElseGet。
# 5. map 与 flatMap
Optional<String> name = optionalUser.map(User::getName);
如果映射函数本身返回 Optional:
Optional<Address> address = optionalUser.flatMap(User::getAddressOptional);
flatMap 避免嵌套:
Optional<Optional<Address>>
│ flatMap
▼
Optional<Address>
# 6. filter
Optional<User> activeUser = optionalUser
.filter(User::isActive);
如果有值且满足条件,保留;否则变 empty。
# 7. Optional 使用边界
推荐用于:
- 方法返回值。
- 查询可能无结果。
- 链式处理可空结果。
不推荐用于:
- 实体字段。
- DTO 字段。
- 方法参数。
- 集合元素大量包装。
不推荐:
public void save(Optional<User> user) {
}
参数可空时,直接校验或重载方法通常更清楚。
# 8. Optional 与集合
不要返回 Optional<List<T>> 表达无结果。
推荐:
List<User> queryUsers();
无结果返回空集合:
return Collections.emptyList();
集合本身已经能表达“没有元素”。
# 9. 常见误区
- 把
Optional.get()当普通 getter。 - 用 Optional 字段影响序列化和 ORM。
- 过度链式调用导致可读性下降。
- 用 Optional 包装集合。
- 方法参数使用 Optional 增加调用成本。
# 10. 空值设计方法论
Optional 不是语法装饰,而是一种 API 契约。它告诉调用方:“这里没有结果是正常业务分支,不是异常。”
查询用户
├─ 找到:Optional.of(user)
└─ 未找到:Optional.empty()
调用方
├─ map 转换
├─ filter 过滤
├─ orElseGet 兜底
└─ orElseThrow 转业务异常
不同空语义要用不同表达:
| 语义 | 推荐表达 | 示例 |
|---|---|---|
| 单个结果可能不存在 | Optional<T> | findById |
| 多个结果为空 | 空集合 | queryOrders |
| 参数非法 | 抛异常 | requireNonNull |
| 字段可选 | 普通字段 + 文档/校验 | DTO、实体 |
| 远程调用失败 | 异常或 Result | 和“无结果”区分 |
不要把所有 null 都机械替换为 Optional。例如数据库实体、JSON DTO、RPC 入参里使用 Optional,往往会增加序列化、框架绑定和调用方理解成本。
专家级空值处理通常遵循:
- 输入参数尽早校验。
- 内部逻辑尽量减少可空变量传播。
- 返回值用类型表达正常的“无结果”。
- 异常表达非预期失败。
- 集合无结果返回空集合。
# Tips 快问快答
Q:Optional 是为了完全替代 null 吗? A:不是,它主要用于返回值显式表达可能为空。
Q:Optional.get 推荐用吗? A:不推荐,除非已经明确判断有值。
Q:orElse 和 orElseGet 区别是什么? A:orElse 参数提前计算,orElseGet 为空时才计算。
Q:Optional 适合做字段吗? A:通常不适合,尤其是实体、DTO、序列化对象。
Q:方法参数应该用 Optional 吗? A:通常不推荐,直接校验或重载更清晰。
Q:查询列表无结果返回什么?
A:返回空集合,不要返回 null 或 Optional<List<T>>。
Q:map 和 flatMap 区别? A:map 转换普通值,flatMap 展开 Optional 结果。
Q:Optional 能避免所有 NPE 吗? A:不能,错误使用仍然会产生异常或空语义混乱。