Optional 使用最佳实践
Optional 是 Java 8 引入的,用来解决"返回值可能为空"的问题。但很多人用 Optional 的方式反而让代码更复杂了。
我见过这样的代码:
// ❌ 滥用 Optional
public Optional<String> findUser(Long id) {
return Optional.ofNullable(userRepository.findById(id));
}
// 调用时
Optional<String> user = findUser(1L);
if (user.isPresent()) {
System.out.println(user.get());
}
这完全没发挥 Optional 的优势,反而多了不必要的包装。
今天我们就来把 Optional 的正确用法讲清楚。
一、Optional 的基本概念
1.1 为什么需要 Optional
// 传统的空指针检查
User user = userService.findById(1L);
if (user != null) {
String name = user.getName();
if (name != null) {
System.out.println(name.toUpperCase());
}
}
// 使用 Optional
Optional<User> user = userService.findById(1L);
user.map(User::getName)
.map(String::toUpperCase)
.ifPresent(System.out::println);
Optional 把"空值检查"变成了链式调用,代码更简洁。
1.2 创建 Optional
// 1. 空 Optional
Optional<String> empty = Optional.empty();
// 2. 非空 Optional(不能传 null)
Optional<String> present = Optional.of("hello");
// Optional.of(null) 会抛出 NullPointerException
// 3. 可空 Optional(推荐)
Optional<String> nullable = Optional.ofNullable(getValue());
// ofNullable(null) 返回 Optional.empty()
1.3 Optional 的三个状态
Optional 有三个状态:
┌─────────────────────────────────────────┐
│ Optional<T> │
├─────────────────────────────────────────┤
│ │
│ Empty │ Present(value) │ │
│ (无值) │ (有值) │ │
│ │
└─────────────────────────────────────────┘
isPresent() = true 表示有值
isEmpty() = true 表示无值(Java 11+)
get() = 获取值(无值时抛异常)
二、正确的使用方式
2.1 map 和 flatMap:转换值
Optional<User> user = userService.findById(1L);
// map:转换值
Optional<String> name = user.map(User::getName);
// 如果 user 有值,返回 Optional.of(user.getName())
// 如果 user 为空,返回 Optional.empty()
// flatMap:处理嵌套 Optional
Optional<Optional<Address>> addressOpt = user.map(User::getAddress);
Optional<Address> address = addressOpt.orElse(Optional.empty());
// 等价于
Optional<Address> address = user.flatMap(User::getAddress);
2.2 filter:条件过滤
Optional<User> user = userService.findById(1L);
// 过滤出年龄大于 18 的用户
Optional<User> adult = user.filter(u -> u.getAge() > 18);
// 过滤出名字以 "A" 开头的用户
Optional<User> aUser = user.filter(u -> u.getName().startsWith("A"));
2.3 orElse / orElseGet / orElseThrow
Optional<String> opt = findName();
// 1. orElse:无论如何都会执行参数的值
String result1 = opt.orElse("default"); // 即使 opt 有值,也创建了 "default"
// 2. orElseGet:只在 opt 为空时才执行
String result2 = opt.orElseGet(() -> computeDefault()); // 延迟加载
// 3. orElseThrow:为空时抛出异常
String result3 = opt.orElseThrow(() -> new RuntimeException("No name found"));
// 性能区别:
// 如果 findName() 返回了值
opt.orElse("default") // 创建了不必要的 "default" 字符串
opt.orElseGet(() -> "default") // Lambda 不执行,更快
💡
优先使用 orElseGet 而不是 orElse,除非默认值是常量。
2.4 ifPresent 和 ifPresentOrElse
Optional<String> name = findName();
// 有值时执行
name.ifPresent(n -> System.out.println("Found: " + n));
// Java 9+:有值或无值时都执行
name.ifPresentOrElse(
n -> System.out.println("Found: " + n),
() -> System.out.println("Not found")
);
三、常见的反模式
3.1 ❌ 反模式 1:isPresent() + get()
// ❌ 错误用法
Optional<User> user = userService.findById(1L);
if (user.isPresent()) {
System.out.println(user.get());
}
// 等价于
User user = userService.findById(1L);
if (user != null) {
System.out.println(user);
}
这是把 Optional 当 nullable 用,完全没发挥它的优势。
3.2 ✅ 正确用法
// ✅ 正确用法 1:ifPresent
user.ifPresent(System.out::println);
// ✅ 正确用法 2:orElse
String name = user.map(User::getName).orElse("匿名");
// ✅ 正确用法 3:orElseThrow
String name = user.map(User::getName)
.orElseThrow(() -> new UserNotFoundException());
// ✅ 正确用法 4:链式调用
user.map(User::getAddress)
.flatMap(Address::getCity)
.ifPresent(System.out::println);
3.3 ❌ 反模式 2:Optional 作为字段
// ❌ 不推荐:Optional 作为字段
public class User {
private Optional<String> nickname; // ❌ 不要这样做
public Optional<String> getNickname() {
return nickname;
}
}
Optional 本身已经是一个包装,再包一层字段没有必要。直接用 nullable 更好。
3.4 ❌ 反模式 3:Optional 作为方法参数
// ❌ 不推荐:Optional 作为参数
public void process(String name, Optional<String> optional) {
// ...
}
// 调用时
process("Alice", Optional.of("value")); // 太繁琐
process("Alice", null); // 还是可能传 null
更好的做法:
// ✅ 方案 1:重载
public void process(String name) {
process(name, null);
}
public void process(String name, String optional) {
// ...
}
// ✅ 方案 2:使用 null 表示不需要
public void process(String name, String optional) {
// optional == null 表示没有传
}
3.5 ❌ 反模式 4:Optional 在集合中
// ❌ 不推荐
List<Optional<String>> list = new ArrayList<>();
// 正确做法
Map<String, String> map = new HashMap<>();
// 用 Map 的 containsKey 或 getOrDefault 表示有无
四、Optional 在实际场景中的应用
4.1 作为返回类型
public class UserService {
// ✅ 推荐:返回 Optional
public Optional<User> findById(Long id) {
return Optional.ofNullable(userRepository.findById(id));
}
// ✅ 推荐:链式调用处理结果
public String getUserCity(Long userId) {
return userService.findById(userId)
.map(User::getAddress)
.flatMap(Address::getCity)
.map(City::getName)
.orElse("未知");
}
}
4.2 使用 Optional 处理嵌套结构
// 传统方式
if (user != null) {
Address address = user.getAddress();
if (address != null) {
City city = address.getCity();
if (city != null) {
System.out.println(city.getName());
}
}
}
// Optional 方式
Optional.ofNullable(user)
.map(User::getAddress)
.flatMap(Optional::ofNullable) // 处理可能的 null
.map(Address::getCity)
.map(City::getName)
.ifPresent(System.out::println);
4.3 Optional 的组合操作
// 场景:计算用户订单的总金额
Optional<User> user = userService.findById(1L);
double totalAmount = user
.map(User::getOrders) // List<Order>
.orElse(Collections.emptyList()) // 空列表
.stream()
.mapToDouble(Order::getAmount)
.sum();
// 场景:获取第一个管理员的名称
Optional<String> adminName = roles.stream()
.filter(role -> role.isAdmin())
.findFirst()
.map(Role::getName);
五、Optional 与 Stream 的对比
Optional 和 Stream 有一些相似之处:
// Optional 的 map/flatMap/filter 和 Stream 类似
Optional<String> name = ...;
Optional<Integer> length = name
.filter(n -> n.length() > 3) // filter
.map(String::length); // map
// Stream 的 map/flatMap/filter
Stream<String> names = ...;
Stream<Integer> lengths = names
.filter(n -> n.length() > 3) // filter
.map(String::length); // map
区别在于:
六、【直观类比】
【直观类比】
Optional 就像一个盒子:
Optional<String> opt = Optional.of("hello");
│
▼
┌───────────┐
│ Box │
│ ┌───────┐ │
│ │"hello"│ │ ← 可能有值,可能没值
│ └───────┘ │
└───────────┘
无值时:
Optional<String> empty = Optional.empty();
│
▼
┌───────────┐
│ Box │
│ (空) │
└───────────┘
你不用检查盒子是否为空,直接告诉盒子:"如果有东西,帮我做这个处理;否则给我一个默认值。"
七、Java 9+ 的增强
7.1 isEmpty()
// Java 9+
Optional<String> opt = Optional.ofNullable(getValue());
if (opt.isEmpty()) { // 比 !opt.isPresent() 更直观
System.out.println("No value");
}
7.2 or()
// Java 9+:支持 Optional 链接
Optional<String> result = opt1
.or(() -> opt2) // opt1 为空时,返回 opt2
.or(() -> opt3); // opt2 也为空时,返回 opt3
7.3 stream()
// Java 9+:Optional 转 Stream
List<String> names = userService.findAll()
.stream()
.map(User::getNickname)
.flatMap(opt -> opt.stream()) // Optional → Stream
.collect(Collectors.toList());
// 等价于
List<String> names = userService.findAll()
.stream()
.map(User::getNickname)
.filter(Optional::isPresent)
.map(Optional::get)
.collect(Collectors.toList());
八、生产避坑
8.1 ❌ 错误示范:过度使用 Optional
// ❌ 过度使用
public class User {
private String name;
public Optional<String> getName() {
return Optional.ofNullable(name); // 不需要包装
}
}
// 更好的做法
public class User {
private String name;
public String getName() {
return name; // 直接返回,允许 null
}
}
8.2 ❌ 错误示范:返回 Optional 的 list
// ❌ 不要这样
public Optional<List<Order>> getOrders(Long userId) {
List<Order> orders = orderService.findByUserId(userId);
return orders.isEmpty() ? Optional.empty() : Optional.of(orders);
}
// ✅ 应该返回空 list
public List<Order> getOrders(Long userId) {
return orderService.findByUserId(userId); // 返回空 list 即可
}
8.3 ❌ 错误示范:get() 前不检查
Optional<String> name = findName();
// ❌ 可能抛出 NoSuchElementException
String value = name.get();
正确做法:
String value = name.orElse("default");
String value = name.orElseGet(() -> computeDefault());
String value = name.orElseThrow(() -> new RuntimeException("No value"));
九、面试追问链
第一层:基本用法
面试官问:"Optional 怎么用?"
创建用 Optional.ofNullable();判断有无值用 isPresent()、ifPresent();获取值用 get()、orElse()、orElseThrow();转换用 map()、flatMap()、filter()。
第二层:最佳实践
面试官追问:"使用 Optional 有什么最佳实践?"
不要用 isPresent() + get(),应该用 ifPresent()、链式调用或 orElse();不要把 Optional 作为字段或方法参数;使用 orElseGet 而不是 orElse(除非默认值是常量)。
第三层:原理
面试官追问:"Optional 的实现原理是什么?"
Optional 本身是一个包装类,内部有一个 value 字段。有值时包装值,无值时用 EMPTY 单例。不需要额外的 JVM 支持。
【学习小结】
- Optional 替代 null 检查,让代码更简洁
- 正确用法:map、flatMap、filter、ifPresent、orElse
- 避免反模式:isPresent + get、Optional 作为字段/参数
- Optional 是包装,不是数据结构
- Java 9+ 增强了 or()、stream() 等方法