很多人对 Optional 的理解就是"用来判空的",然后在代码里写
optional.get()------这其实完全用错了 。Optional 是 Java 8 引入的、为了在类型系统层面表达"值可能不存在" 的容器类。
目录
-
- [一、Optional 到底是什么(设计哲学)](#一、Optional 到底是什么(设计哲学))
-
- [1.1 官方定义](#1.1 官方定义)
- [1.2 为什么要有 Optional?(解决 NPE 之痛)](#1.2 为什么要有 Optional?(解决 NPE 之痛))
- [1.3 核心设计哲学](#1.3 核心设计哲学)
- [二、Optional 的创建方式](#二、Optional 的创建方式)
-
- [2.1 Optional.of(T value) ------ 值不能为 null](#2.1 Optional.of(T value) —— 值不能为 null)
- [2.2 Optional.ofNullable(T value) ------ 值可以为 null](#2.2 Optional.ofNullable(T value) —— 值可以为 null)
- [2.3 Optional.empty() ------ 创建空 Optional](#2.3 Optional.empty() —— 创建空 Optional)
- [2.4 三种创建方式对比](#2.4 三种创建方式对比)
- [三、Optional 的常用 API 全景](#三、Optional 的常用 API 全景)
-
- [3.1 API 分类总览](#3.1 API 分类总览)
- [3.2 判断类 API](#3.2 判断类 API)
- [3.3 获取值类 API](#3.3 获取值类 API)
- [3.4 消费类 API](#3.4 消费类 API)
- [3.5 转换类 API(核心)](#3.5 转换类 API(核心))
- [四、Optional 的链式操作(核心用法)](#四、Optional 的链式操作(核心用法))
-
- [4.1 场景:层层嵌套的对象查询](#4.1 场景:层层嵌套的对象查询)
- [4.2 场景:多级默认值](#4.2 场景:多级默认值)
- [4.3 场景:条件过滤 + 转换](#4.3 场景:条件过滤 + 转换)
- [4.4 场景:条件执行](#4.4 场景:条件执行)
- [4.5 场景:转换为 Stream(Java 9+)](#4.5 场景:转换为 Stream(Java 9+))
- [五、Optional 的源码剖析](#五、Optional 的源码剖析)
-
- [5.1 类的整体结构](#5.1 类的整体结构)
- [5.2 核心方法源码](#5.2 核心方法源码)
- [5.3 从源码看设计精髓](#5.3 从源码看设计精髓)
- [六、Optional 的 7 大反模式(血泪避坑)](#六、Optional 的 7 大反模式(血泪避坑))
-
- [反模式 1:用 isPresent() + get() 代替判空](#反模式 1:用 isPresent() + get() 代替判空)
- [反模式 2:把 Optional 作为字段](#反模式 2:把 Optional 作为字段)
- [反模式 3:把 Optional 作为方法参数](#反模式 3:把 Optional 作为方法参数)
- [反模式 4:Optional.of(null)](#反模式 4:Optional.of(null))
- [反模式 5:orElse 里做大开销操作](#反模式 5:orElse 里做大开销操作)
- [反模式 6:Optional 嵌套](#反模式 6:Optional 嵌套)
- [反模式 7:用 Optional 替代集合判空](#反模式 7:用 Optional 替代集合判空)
- [七、Optional 的正确使用场景](#七、Optional 的正确使用场景)
-
- [7.1 适用场景](#7.1 适用场景)
- [7.2 不适用场景](#7.2 不适用场景)
- [7.3 一句话原则](#7.3 一句话原则)
- [八、Optional 实战:重构真实代码](#八、Optional 实战:重构真实代码)
-
- [8.1 案例 1:查询用户并转 DTO](#8.1 案例 1:查询用户并转 DTO)
- [8.2 案例 2:多级缓存查询](#8.2 案例 2:多级缓存查询)
- [8.3 案例 3:参数校验 + 业务处理](#8.3 案例 3:参数校验 + 业务处理)
- [8.4 案例 4:Stream 中过滤空值(推荐)](#8.4 案例 4:Stream 中过滤空值(推荐))
- [九、Optional 与其他方案的对比](#九、Optional 与其他方案的对比)
-
- [9.1 Optional vs 传统判空](#9.1 Optional vs 传统判空)
- [9.2 Optional vs Kotlin 的 Null Safety](#9.2 Optional vs Kotlin 的 Null Safety)
- [9.3 Optional vs Guava 的 Optional](#9.3 Optional vs Guava 的 Optional)
- [十、总结:Optional 使用 Checklist](#十、总结:Optional 使用 Checklist)
- 十一、一句话总结
一、Optional 到底是什么(设计哲学)
1.1 官方定义
Java 官方文档对 Optional 的定义是:
A container object which may or may not contain a non-null value.
(一个可能包含也可能不包含非 null 值的容器对象。)
用通俗的话讲:
Optional 就是一个**"盒子"**,里面可能装着东西,也可能是空的。你不需要打开盒子判断里面有没有东西,而是通过盒子提供的方法来处理"有"和"没有"两种情况。
1.2 为什么要有 Optional?(解决 NPE 之痛)
(1)NPE 是 Java 的"头号公敌"
Tony Hoare(图灵奖得主)曾说过,他在 1965 年发明 null 引用是"十亿美元的错误"。Java 继承了这个设计,导致:
java
// 一个经典的 NPE 调用链
String city = user.getAddress().getCity().getName();
这段代码每一步都可能 NPE:
user可能是 nulluser.getAddress()可能返回 nullgetCity()可能返回 nullgetName()可能返回 null
(2)传统的防御式判空
java
String cityName = null;
if (user != null) {
Address address = user.getAddress();
if (address != null) {
City city = address.getCity();
if (city != null) {
cityName = city.getName();
}
}
}
问题:
- 嵌套地狱(Arrow Anti-pattern),可读性极差
- 容易漏判某一层
- 业务逻辑被淹没在判空里
(3)Optional 的解决思路
java
String cityName = Optional.ofNullable(user)
.map(User::getAddress)
.map(Address::getCity)
.map(City::getName)
.orElse("未知城市");
关键差异:
| 维度 | 传统判空 | Optional |
|---|---|---|
| 判空位置 | 每次调用都判 | 只在链式入口判一次 |
| 可读性 | 嵌套地狱 | 扁平链式 |
| 遗漏风险 | 高(容易漏) | 低(API 强制处理) |
| 语义 | 值可能为 null | 类型层面声明"值可能不存在" |
1.3 核心设计哲学
(1)把"可能为空"显式化
传统方法签名:User getUser(Long id) ------ 调用者不知道是否可能返回 null。
Optional 方法签名:Optional<User> getUser(Long id) ------ 调用者立刻知道:"哦,可能查不到"。
这是类型系统层面的改进,让"空值"成为方法契约的一部分。
(2)强制调用者处理"空"的情况
java
Optional<User> optUser = userService.getUser(1L);
// ❌ 直接 get,可能抛 NoSuchElementException
User user = optUser.get();
// ✅ 编译器不强制,但 API 设计鼓励你这样写
User user = optUser.orElseThrow(() -> new BusinessException("用户不存在"));
虽然 Java 编译器不会像 Kotlin 那样强制你处理 null,但 Optional 的 API 设计引导你走向正确的写法。
(3)Optional 不是一个"通用的 null 替代品"
这是最容易被误解的一点。Optional 的设计初衷是作为"方法返回值",而不是:
- ❌ 类的字段
- ❌ 方法参数
- ❌ 集合元素
后面会详细讲为什么。
二、Optional 的创建方式
Optional 提供了 3 个静态工厂方法:
2.1 Optional.of(T value) ------ 值不能为 null
java
// ✅ 值明确不为 null
Optional<String> opt = Optional.of("hello");
// ❌ 传入 null 会抛 NullPointerException
Optional<String> opt2 = Optional.of(null); // 抛 NPE
源码:
java
public static <T> Optional<T> of(T value) {
return new Optional<>(value); // 内部会 Objects.requireNonNull
}
private Optional(T value) {
this.value = Objects.requireNonNull(value);
}
使用场景 :你确信值不为 null 时用。比如常量、刚创建的对象。
2.2 Optional.ofNullable(T value) ------ 值可以为 null
java
// ✅ 值可能为 null
String str = getStr(); // 可能返回 null
Optional<String> opt = Optional.ofNullable(str);
// ✅ 传入 null 也不会抛异常,返回 Optional.empty()
Optional<String> opt2 = Optional.ofNullable(null); // OK
源码:
java
public static <T> Optional<T> ofNullable(T value) {
return value == null ? empty() : of(value);
}
使用场景 :90% 的场景都用这个。因为它安全,不会因为意外的 null 而抛 NPE。
2.3 Optional.empty() ------ 创建空 Optional
java
Optional<String> empty = Optional.empty();
源码:
java
private static final Optional<?> EMPTY = new Optional<>();
public static <T> Optional<T> empty() {
@SuppressWarnings("unchecked")
Optional<T> t = (Optional<T>) EMPTY;
return t;
}
亮点 :empty() 返回的是单例 (EMPTY),所有空 Optional 共享同一个对象,节省内存。
2.4 三种创建方式对比
| 方法 | 传入 null | 使用场景 |
|---|---|---|
Optional.of(T) |
抛 NPE | 值确定不为 null |
Optional.ofNullable(T) |
返回 empty | 通用场景(推荐) |
Optional.empty() |
- | 明确返回空 |
记忆口诀:
不确定用 ofNullable,确定不为 null 用 of,明确返回空用 empty。
三、Optional 的常用 API 全景
先把 API 按"是否返回 Optional"分类,这样你能快速形成知识地图。
3.1 API 分类总览
Optional<T>
├── 创建(静态)
│ ├── of(T)
│ ├── ofNullable(T)
│ └── empty()
│
├── 判断
│ ├── isPresent() → boolean
│ └── isEmpty() → boolean(Java 11+)
│
├── 获取值(终止操作)
│ ├── get() → T(危险,可能抛异常)
│ ├── orElse(T) → T(兜底值,总执行)
│ ├── orElseGet(Supplier) → T(兜底值,懒执行)
│ ├── orElseThrow() → T(为空抛异常)
│ └── orElseThrow(Supplier)→ T(为空抛自定义异常)
│
├── 中间操作(返回 Optional)
│ ├── map(Function) → Optional<U>
│ ├── flatMap(Function) → Optional<U>
│ ├── filter(Predicate) → Optional<T>
│ ├── or(Supplier) → Optional<T>(Java 9+)
│ └── stream() → Stream<T>(Java 9+)
│
└── 消费(终止操作)
├── ifPresent(Consumer) → void
└── ifPresentOrElse(Consumer, Runnable) → void(Java 9+)
3.2 判断类 API
java
Optional<String> opt = Optional.of("hello");
// isPresent:有值返回 true
if (opt.isPresent()) {
System.out.println(opt.get());
}
// isEmpty(Java 11+):无值返回 true
if (opt.isEmpty()) {
System.out.println("没有值");
}
注意 :isPresent() + get() 是最不推荐的写法 ,它等价于 if (x != null) use(x),完全没发挥 Optional 的优势。后面会讲更好的替代方案。
3.3 获取值类 API
java
Optional<String> opt = Optional.of("hello");
// ✅ get():有值返回,无值抛 NoSuchElementException
String s1 = opt.get(); // "hello"
// ✅ orElse(T):无值时返回默认值(无论有没有值,default 都会执行)
String s2 = opt.orElse("default"); // "hello"
// ✅ orElseGet(Supplier):无值时调用 Supplier 获取默认值(懒执行)
String s3 = opt.orElseGet(() -> "computed default"); // "hello"
// ✅ orElseThrow():无值时抛 NoSuchElementException(Java 10+)
String s4 = opt.orElseThrow();
// ✅ orElseThrow(Supplier):无值时抛自定义异常
String s5 = opt.orElseThrow(() -> new BusinessException("值不存在"));
orElse vs orElseGet 的坑(重点!):
java
// ❌ orElse:无论 Optional 是否有值,都会执行 createDefault()
Optional<User> opt = Optional.of(existingUser);
User user = opt.orElse(createDefaultUser()); // createDefaultUser 总会被执行!
// ✅ orElseGet:只有 Optional 为空时才执行
User user = opt.orElseGet(() -> createDefaultUser()); // 有值时不会执行
为什么? 看源码:
java
// orElse:参数是"值",传参时就已经求值了
public T orElse(T other) {
return value != null ? value : other; // other 在调用前就计算好了
}
// orElseGet:参数是"Supplier",只有需要时才调用 get()
public T orElseGet(Supplier<? extends T> supplier) {
return value != null ? value : supplier.get();
}
结论 :如果默认值的创建有副作用或开销大 (如数据库查询、远程调用),必须用 orElseGet。
3.4 消费类 API
java
Optional<String> opt = Optional.of("hello");
// ifPresent:有值时执行 Consumer
opt.ifPresent(s -> System.out.println("值: " + s));
// ifPresentOrElse(Java 9+):有值走前者,无值走后者
opt.ifPresentOrElse(
s -> System.out.println("值: " + s),
() -> System.out.println("没有值")
);
对比传统写法:
java
// 传统
if (user != null) {
sendEmail(user);
} else {
log.warn("user 不存在");
}
// Optional(Java 9+)
Optional.ofNullable(user).ifPresentOrElse(
this::sendEmail,
() -> log.warn("user 不存在")
);
3.5 转换类 API(核心)
这是 Optional 最强大的部分,也是能大幅简化代码的关键。
java
// map:值转换(如果原 Optional 为空,直接返回空,不执行 mapper)
Optional<User> optUser = getUser();
Optional<String> optName = optUser.map(User::getName);
// flatMap:值转换,但 mapper 返回的已经是 Optional(避免嵌套)
Optional<Address> optAddr = optUser.flatMap(User::getAddressOptional);
// filter:过滤,不满足条件返回 empty
Optional<User> optAdult = optUser.filter(u -> u.getAge() >= 18);
map vs flatMap 的区别(面试高频):
java
// map:Function<T, U>,返回 Optional<U>
Optional<String> name = optUser.map(User::getName);
// 如果 User::getName 返回 String,那 map 后是 Optional<String>
// flatMap:Function<T, Optional<U>>,返回 Optional<U>
Optional<Address> addr = optUser.flatMap(User::getAddressOptional);
// 如果 User::getAddressOptional 返回 Optional<Address>,必须用 flatMap
通俗理解:
map里你返回普通值flatMap里你返回 Optional 值 (避免Optional<Optional<X>>)
如果搞错了会怎样?
java
// 用错 map:
Optional<Optional<Address>> nested = optUser.map(User::getAddressOptional);
// 嵌套两层,很难处理
// 用 flatMap:
Optional<Address> flat = optUser.flatMap(User::getAddressOptional);
// 扁平,好处理
四、Optional 的链式操作(核心用法)
Optional 的真正威力在于链式调用。这一节我会用真实的例子,让你看到它有多香。
4.1 场景:层层嵌套的对象查询
假设有这样一个调用链:订单 → 用户 → 地址 → 城市 → 城市名
java
class Order {
private User user;
public User getUser() { return user; }
}
class User {
private Address address;
public Address getAddress() { return address; }
}
class Address {
private City city;
public City getCity() { return city; }
}
class City {
private String name;
public String getName() { return name; }
}
传统写法(嵌套地狱):
java
public String getCityName(Order order) {
if (order != null) {
User user = order.getUser();
if (user != null) {
Address address = user.getAddress();
if (address != null) {
City city = address.getCity();
if (city != null) {
return city.getName();
}
}
}
}
return "未知城市";
}
Optional 写法:
java
public String getCityName(Order order) {
return Optional.ofNullable(order)
.map(Order::getUser)
.map(User::getAddress)
.map(Address::getCity)
.map(City::getName)
.orElse("未知城市");
}
对比:
- 代码行数:15 行 → 7 行
- 缩进层级:4 层 → 1 层
- 可读性:⭐⭐ → ⭐⭐⭐⭐⭐
这就是 Optional 链式的核心价值:把"嵌套判空"变成"扁平流水线"。
4.2 场景:多级默认值
需求:从缓存、数据库、默认配置依次查找,找到第一个就返回。
java
// 传统写法
public String getConfig(String key) {
String value = cache.get(key);
if (value != null) {
return value;
}
value = database.get(key);
if (value != null) {
return value;
}
return DEFAULT_VALUE;
}
// Optional + or(Java 9+)
public String getConfig(String key) {
return Optional.ofNullable(cache.get(key))
.or(() -> Optional.ofNullable(database.get(key)))
.orElse(DEFAULT_VALUE);
}
or 的妙用 :它接受一个 Supplier<Optional<T>>,当原 Optional 为空时,才去调用 Supplier 获取备选值。懒执行,性能友好。
4.3 场景:条件过滤 + 转换
需求:获取用户 VIP 等级,非 VIP 返回"普通用户"。
java
// 传统写法
public String getVipLevel(User user) {
if (user != null && user.isVip()) {
VipInfo vip = user.getVipInfo();
if (vip != null) {
return vip.getLevel();
}
}
return "普通用户";
}
// Optional 写法
public String getVipLevel(User user) {
return Optional.ofNullable(user)
.filter(User::isVip)
.map(User::getVipInfo)
.map(VipInfo::getLevel)
.orElse("普通用户");
}
4.4 场景:条件执行
需求:如果用户存在,发邮件;否则记录日志。
java
// 传统写法
if (user != null) {
sendEmail(user);
} else {
log.warn("用户不存在");
}
// Optional + ifPresentOrElse(Java 9+)
Optional.ofNullable(user).ifPresentOrElse(
this::sendEmail,
() -> log.warn("用户不存在")
);
4.5 场景:转换为 Stream(Java 9+)
需求:一个 Optional 列表,只取有值的,收集为新列表。
java
List<Optional<User>> optionalList = ...;
// 传统写法
List<User> users = new ArrayList<>();
for (Optional<User> opt : optionalList) {
if (opt.isPresent()) {
users.add(opt.get());
}
}
// Optional.stream()(Java 9+)
List<User> users = optionalList.stream()
.flatMap(Optional::stream) // 空 Optional 自动过滤
.collect(Collectors.toList());
Optional.stream() 的妙用:把 Optional 转成 Stream,空 Optional 转成空 Stream,非常适合在 Stream 管道里过滤"可能为空"的元素。
五、Optional 的源码剖析
5.1 类的整体结构
java
public final class Optional<T> {
/** 空单例 */
private static final Optional<?> EMPTY = new Optional<>();
/** 内部持有的值 */
private final T value;
/** 私有构造:传入 null 会抛 NPE */
private Optional(T value) {
this.value = Objects.requireNonNull(value);
}
/** 私有空构造:给 EMPTY 用 */
private Optional() {
this.value = null;
}
// ... 方法
}
关键设计点:
final class:不可继承,保证行为一致private final T value:不可变,线程安全- 私有构造:只能通过静态工厂方法创建
EMPTY单例:所有空 Optional 共享一个对象,节省内存
5.2 核心方法源码
(1)of / ofNullable / empty
java
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);
}
public static <T> Optional<T> empty() {
@SuppressWarnings("unchecked")
Optional<T> t = (Optional<T>) EMPTY;
return t;
}
(2)get
java
public T get() {
if (value == null) {
throw new NoSuchElementException("No value present");
}
return value;
}
(3)orElse vs orElseGet(重点)
java
public T orElse(T other) {
return value != null ? value : other; // other 是"已求值的参数"
}
public T orElseGet(Supplier<? extends T> supplier) {
return value != null ? value : supplier.get(); // 懒执行
}
public T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X {
if (value != null) {
return value;
} else {
throw exceptionSupplier.get();
}
}
(4)map
java
public <U> Optional<U> map(Function<? super T, ? extends U> mapper) {
Objects.requireNonNull(mapper);
if (!isPresent()) {
return empty(); // 原值为空,直接返回空,不调用 mapper
} else {
return Optional.ofNullable(mapper.apply(value));
}
}
关键点 :map 的结果用 ofNullable 包装,意味着如果 mapper 返回 null,结果是 empty,而不是抛 NPE。这是很贴心的设计。
(5)flatMap
java
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
}
}
关键点 :flatMap 要求 mapper 返回的 Optional 不能为 null ,否则抛 NPE。这是和 map 的重要区别。
(6)filter
java
public Optional<T> filter(Predicate<? super T> predicate) {
Objects.requireNonNull(predicate);
if (!isPresent()) {
return this; // 原值为空,直接返回
} else {
return predicate.test(value) ? this : empty();
}
}
(7)ifPresent / ifPresentOrElse
java
public void ifPresent(Consumer<? super T> action) {
if (value != null) {
action.accept(value);
}
}
public void ifPresentOrElse(Consumer<? super T> action, Runnable emptyAction) {
if (value != null) {
action.accept(value);
} else {
emptyAction.run();
}
}
5.3 从源码看设计精髓
| 设计点 | 好处 |
|---|---|
final class |
防止继承破坏语义 |
private final T value |
不可变,线程安全 |
EMPTY 单例 |
节省内存 |
map 内部用 ofNullable |
容忍 mapper 返回 null |
flatMap 用 requireNonNull |
强制 mapper 返回 Optional,避免嵌套 |
orElseGet 用 Supplier |
支持懒执行 |
六、Optional 的 7 大反模式(血泪避坑)
这一节是重点中的重点,Optional 用错的地方比用对的多。我列出实际项目中见到最多的 7 个反模式。
反模式 1:用 isPresent() + get() 代替判空
java
// ❌ 反模式:完全没发挥 Optional 的优势
Optional<User> optUser = getUser();
if (optUser.isPresent()) {
User user = optUser.get();
System.out.println(user.getName());
}
// ✅ 正确:用 ifPresent
getUser().ifPresent(user -> System.out.println(user.getName()));
// ✅ 或者:用 orElse / orElseGet / orElseThrow 处理
User user = getUser().orElseThrow(() -> new BusinessException("用户不存在"));
为什么是反模式?
- 代码比
if (user != null)还长 - 依旧有"忘记判空"的风险(用
get()直接取) - 没有表达出"默认值"、"抛异常"等意图
判断标准 :只要你写了 isPresent() + get(),几乎一定有更好的写法。
反模式 2:把 Optional 作为字段
java
// ❌ 反模式
public class User {
private Optional<String> nickname; // 不要这样!
private Optional<Address> address; // 不要这样!
}
// ✅ 正确:字段直接用普通引用,需要时再包装
public class User {
private String nickname; // 可能为 null
private Address address; // 可能为 null
public Optional<String> getNickname() {
return Optional.ofNullable(nickname); // 返回时包装
}
}
为什么是反模式?
- Optional 没有实现 Serializable(Java 11 前),无法直接序列化
- 增加内存开销:每个字段多一个 Optional 对象头
- 违反 Optional 的设计初衷 :它是为返回值设计的
- JPA/Hibernate 等框架无法直接映射
正确做法:字段用普通类型,在 getter 返回时包装成 Optional。
反模式 3:把 Optional 作为方法参数
java
// ❌ 反模式
public void updateUser(Long id, Optional<String> nickname) {
// ...
}
// ✅ 正确:用重载或普通参数
public void updateUser(Long id, String nickname) {
// nickname 可能为 null,内部判空
}
// ✅ 或者用重载明确意图
public void updateNickname(Long id, String nickname) { }
public void clearNickname(Long id) { }
为什么是反模式?
- 调用方更麻烦 :要写
Optional.ofNullable(x)或Optional.empty() - 没有表达价值:方法参数传 null 是常见场景,用 Optional 反而增加噪音
- 官方明确不推荐:Optional 的设计者 Brian Goetz 曾说过
"Optional is primarily intended for use as a method return type where there is a clear need to represent 'no result'."
唯一例外 :当你需要区分"未提供 "和"提供了 null"时,可以用 Optional 参数。但这种场景极少,一般用其他方式(如重载、Builder 模式)更好。
反模式 4:Optional.of(null)
java
// ❌ 反模式:直接抛 NPE
Optional<String> opt = Optional.of(getStr()); // 如果 getStr() 返回 null,炸
// ✅ 正确
Optional<String> opt = Optional.ofNullable(getStr());
记住口诀 :不确定用 ofNullable。
反模式 5:orElse 里做大开销操作
java
// ❌ 反模式:无论有没有值,都查一次数据库
User user = getUser().orElse(userDao.findDefault());
// ✅ 正确:只有为空时才查
User user = getUser().orElseGet(() -> userDao.findDefault());
这是最容易犯的错 。orElse 的参数是即时求值 的,orElseGet 的参数是懒执行的。
判断标准:
- 默认值是常量或简单对象 →
orElse - 默认值是方法调用、数据库查询、远程调用 →
orElseGet
反模式 6:Optional 嵌套
java
// ❌ 反模式:Optional<Optional<X>>
Optional<Optional<Address>> nested = optUser.map(User::getAddressOptional);
// ✅ 正确:用 flatMap
Optional<Address> flat = optUser.flatMap(User::getAddressOptional);
记忆:map 里返回普通值,flatMap 里返回 Optional。
反模式 7:用 Optional 替代集合判空
java
// ❌ 反模式
public Optional<List<User>> getUsers() {
return Optional.ofNullable(userList); // 集合为空和 null 语义不同
}
// ✅ 正确:集合返回空集合,绝不返回 null
public List<User> getUsers() {
return userList == null ? Collections.emptyList() : userList;
}
为什么是反模式?
- 集合的最佳实践是"返回空集合而非 null",Optional 没必要
- 调用方处理两个空场景(Optional 空 + List 空)反而更复杂
- 增加了不必要的包装
特例 :当"没有用户列表"和"用户列表为空"是两种不同业务语义时,可以用 Optional 包裹。但这种场景极少。
七、Optional 的正确使用场景
经过上面的避坑,我们总结 Optional 的正确使用场景:
7.1 适用场景
| 场景 | 示例 |
|---|---|
| ✅ 方法返回值可能不存在 | Optional<User> findById(Long id) |
| ✅ 链式调用防 NPE | Optional.ofNullable(a).map(A::getB).map(B::getC) |
| ✅ 表达"未找到"语义 | Optional<Config> getConfig(String key) |
| ✅ 多级默认值 | opt.or(() -> fallback).orElse(default) |
| ✅ 在 Stream 中过滤 | list.stream().flatMap(Optional::stream) |
| ✅ 替代"返回 null"约定 | 明确告诉调用者"可能没有" |
7.2 不适用场景
| 场景 | 原因 |
|---|---|
| ❌ 类字段 | 无法序列化、内存开销、框架不兼容 |
| ❌ 方法参数 | 调用麻烦、无表达价值 |
| ❌ 集合返回值 | 应返回空集合而非 Optional |
| ❌ 简单判空 | 用 if (x != null) 更直观 |
| ❌ 性能敏感的高频调用 | Optional 对象本身有开销 |
7.3 一句话原则
Optional 用于"方法返回值",用于"链式防 NPE",用于"表达可能不存在的语义"。其他场景慎用。
八、Optional 实战:重构真实代码
8.1 案例 1:查询用户并转 DTO
重构前:
java
public UserDTO getUserDTO(Long id) {
User user = userMapper.selectById(id);
if (user == null) {
return null;
}
UserDTO dto = new UserDTO();
dto.setId(user.getId());
dto.setName(user.getName());
if (user.getAddress() != null) {
dto.setCity(user.getAddress().getCity());
}
return dto;
}
重构后:
java
public Optional<UserDTO> getUserDTO(Long id) {
return Optional.ofNullable(userMapper.selectById(id))
.map(this::toDTO);
}
private UserDTO toDTO(User user) {
UserDTO dto = new UserDTO();
dto.setId(user.getId());
dto.setName(user.getName());
Optional.ofNullable(user.getAddress())
.map(Address::getCity)
.ifPresent(dto::setCity);
return dto;
}
8.2 案例 2:多级缓存查询
重构前:
java
public String getConfig(String key) {
String value = localCache.get(key);
if (value != null) return value;
value = redisCache.get(key);
if (value != null) {
localCache.put(key, value);
return value;
}
value = db.get(key);
if (value != null) {
redisCache.put(key, value);
localCache.put(key, value);
return value;
}
return DEFAULT_VALUE;
}
重构后(Java 9+):
java
public String getConfig(String key) {
return Optional.ofNullable(localCache.get(key))
.or(() -> Optional.ofNullable(redisCache.get(key))
.map(v -> { localCache.put(key, v); return v; }))
.or(() -> Optional.ofNullable(db.get(key))
.map(v -> {
redisCache.put(key, v);
localCache.put(key, v);
return v;
}))
.orElse(DEFAULT_VALUE);
}
评价 :这个重构有争议。链式写法虽然"炫技",但加了缓存的副作用后,可读性下降。实际项目中,我倾向于保留传统写法,或者用更清晰的多级缓存封装。
启示 :Optional 不是万能的,别为了炫技而用。可读性优先。
8.3 案例 3:参数校验 + 业务处理
重构前:
java
public void transfer(Long fromId, Long toId, BigDecimal amount) {
if (amount == null || amount.compareTo(BigDecimal.ZERO) <= 0) {
throw new IllegalArgumentException("金额必须大于0");
}
User from = userMapper.selectById(fromId);
if (from == null) {
throw new BusinessException("转出用户不存在");
}
if (from.getBalance().compareTo(amount) < 0) {
throw new BusinessException("余额不足");
}
// 转账逻辑...
}
重构后:
java
public void transfer(Long fromId, Long toId, BigDecimal amount) {
Optional.ofNullable(amount)
.filter(a -> a.compareTo(BigDecimal.ZERO) > 0)
.orElseThrow(() -> new IllegalArgumentException("金额必须大于0"));
User from = Optional.ofNullable(userMapper.selectById(fromId))
.orElseThrow(() -> new BusinessException("转出用户不存在"));
Optional.of(from)
.filter(u -> u.getBalance().compareTo(amount) >= 0)
.orElseThrow(() -> new BusinessException("余额不足"));
// 转账逻辑...
}
评价 :虽然可以这样写,但传统 if-throw 更直观。Optional 在这里有点"用力过猛"。
启示 :Optional 适合"链式转换 + 默认值",不适合"参数校验 + 抛异常"。
8.4 案例 4:Stream 中过滤空值(推荐)
重构前:
java
List<Order> orders = ...;
List<User> users = new ArrayList<>();
for (Order order : orders) {
User user = order.getUser();
if (user != null) {
users.add(user);
}
}
重构后:
java
List<User> users = orders.stream()
.map(Order::getUser) // Stream<User>,可能含 null
.filter(Objects::nonNull) // 过滤 null
.collect(Collectors.toList());
或者用 Optional(Java 9+):
java
List<User> users = orders.stream()
.map(order -> Optional.ofNullable(order.getUser()))
.flatMap(Optional::stream) // 空 Optional 转空 Stream,自动过滤
.collect(Collectors.toList());
评价 :这个场景非常推荐 。特别是当你需要"map 后再 filter null"时,Optional::stream 是利器。
九、Optional 与其他方案的对比
9.1 Optional vs 传统判空
| 维度 | 传统判空 | Optional |
|---|---|---|
| 可读性(简单场景) | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ |
| 可读性(链式场景) | ⭐⭐ | ⭐⭐⭐⭐⭐ |
| 性能 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
| 类型安全 | ⭐⭐ | ⭐⭐⭐⭐ |
| 学习成本 | 低 | 中 |
结论 :简单场景用传统判空,链式场景用 Optional。
9.2 Optional vs Kotlin 的 Null Safety
kotlin
// Kotlin 原生支持可空类型
val user: User? = getUser()
val name = user?.address?.city?.name ?: "未知城市"
Kotlin 的优势:
- 编译期强制检查
- 语法更简洁(
?.、?:) - 无运行时开销
Java Optional 的局限:
- 不是编译器强制,靠自觉
- 语法相对繁琐
- 有对象创建开销
评价:Kotlin 的方案确实更好,但 Java 项目里 Optional 是最接近的替代品。
9.3 Optional vs Guava 的 Optional
java
// Guava
com.google.common.base.Optional<User> opt = Optional.fromNullable(user);
// Java 8+
java.util.Optional<User> opt = Optional.ofNullable(user);
对比:
| 特性 | Guava Optional | Java Optional |
|---|---|---|
| 静态方法 | fromNullable |
ofNullable |
| map/flatMap | ✅ | ✅ |
| filter | ✅ | ✅ |
| orElseGet | or(Supplier) |
orElseGet(Supplier) |
| 转 Stream | ❌ | ✅(Java 9+) |
| 官方支持 | ❌ | ✅ |
结论 :新项目统一用 java.util.Optional,别再引入 Guava 的。
十、总结:Optional 使用 Checklist
| 检查项 | 说明 |
|---|---|
✅ 优先用 Optional.ofNullable |
不确定是否 null 时 |
✅ 用 orElseGet 而非 orElse |
默认值有开销时(懒执行) |
✅ 用 flatMap 处理返回 Optional 的场景 |
避免嵌套 |
✅ 用 map 链式转换 |
替代嵌套判空 |
✅ 用 ifPresent / ifPresentOrElse |
替代 isPresent + get |
✅ 用 orElseThrow 表达"必须有值" |
语义清晰 |
| ✅ 字段用普通类型,getter 返回 Optional | 别把 Optional 当字段 |
| ✅ 方法参数不用 Optional | 用重载或普通参数 |
| ✅ 集合返回空集合,不返回 Optional | 集合最佳实践 |
❌ 不用 isPresent() + get() |
反模式 |
❌ 不用 Optional.of(null) |
会抛 NPE |
| ❌ 不用 Optional 嵌套 | 用 flatMap |
| ❌ 不为了用而用 | 可读性优先 |
十一、一句话总结
Optional 是"可能不存在的值"的容器,用于方法返回值和链式防 NPE。用对了是神器,用错了是负担。记住核心原则:简单场景传统判空,链式场景 Optional;获取值优先 orElseGet/orElseThrow,转换用 map/flatMap;字段、参数、集合别用 Optional。