Java Optional 很好,但别把它当万能锤子
Java Optional 从入门到最佳实践
适用版本:Java 8+
重点覆盖:Optional 介绍、为什么要引入 Optional、基本使用方法、最佳实践、不合理使用场景。
一、Optional 介绍 Introduction to Optional
Optional<T> 是 Java 8 引入的一个容器类,位于 java.util 包下。它表示一个值可能存在,也可能不存在:
- 有值:
Optional.of(value)
- 无值:
Optional.empty()
- 值可能为
null:Optional.ofNullable(value)
从语义上看,Optional<T> 不是为了“把所有 null 都包装起来”,而是为了在方法返回值中明确表达:
这个方法可能找不到结果,调用方必须认真处理这种情况。
官方 API 文档中也明确提到,Optional 主要适合用作方法返回类型,用来表达“没有结果”的情况,尤其是直接返回 null 容易导致错误时。
一个最典型的例子:
1 2 3 4
| public Optional<User> findById(Long userId) { User user = userRepository.findById(userId); return Optional.ofNullable(user); }
|
调用方看到返回值是 Optional<User>,就会意识到:
1
| Optional<User> userOptional = userService.findById(1L);
|
这里不是“肯定有用户”,而是“可能有,也可能没有”。这就是 Optional 最大的价值:让空值语义显式化。
二、为什么要引入 Optional?Why use Optional?
1. 减少隐式 null 带来的 NPE 风险
传统 Java 代码中,方法返回 null 很常见:
1 2 3
| public User findById(Long userId) { return userRepository.findById(userId); }
|
调用方如果忘记判空:
1 2
| User user = userService.findById(1L); String name = user.getName();
|
一旦 user 为 null,就会抛出 NullPointerException。
使用 Optional 后,返回值的类型本身就在提醒调用方:
1 2 3
| public Optional<User> findById(Long userId) { return Optional.ofNullable(userRepository.findById(userId)); }
|
调用方必须选择一种处理方式:
1 2 3
| String name = userService.findById(1L) .map(User::getName) .orElse("Unknown");
|
这并不是说 Optional 能彻底消灭 NPE,而是说它能把“可能为空”从隐藏约定变成显式类型。
2. 提高 API 的表达力
下面两个方法签名,表达力完全不同:
1
| public User getCurrentUser();
|
1
| public Optional<User> getCurrentUser();
|
第一个方法看不出来是否可能返回 null。调用方只能看文档、看实现,或者踩坑。
第二个方法直接告诉你:当前用户可能不存在。
在业务代码中,Optional 很适合表达这些语义:
- 根据 ID 查询数据,数据可能不存在。
- 从缓存中读取值,缓存可能未命中。
- 从请求上下文中获取用户,用户可能未登录。
- 从配置中读取可选配置,配置可能未设置。
- 从集合中查找第一个符合条件的元素,可能找不到。
例如:
1 2 3 4 5
| public Optional<Coupon> findAvailableCoupon(Long userId) { return couponRepository.findByUserId(userId).stream() .filter(Coupon::isAvailable) .findFirst(); }
|
这里的 Optional<Coupon> 比返回 null 更清楚,也比抛异常更自然,因为“没有可用优惠券”通常不是异常,而是一种正常业务结果。
3. 配合 Stream 和函数式写法
Java 8 同时引入了 Lambda、Stream 和 Optional。它们放在一起时,可以让一些判空、过滤、转换逻辑更紧凑:
1 2 3 4
| String email = userService.findById(userId) .filter(User::isActive) .map(User::getEmail) .orElse("no-email@example.com");
|
这段代码表达的是:
- 查询用户。
- 用户存在并且是启用状态。
- 获取邮箱。
- 如果任一步没有结果,就返回默认邮箱。
如果用传统写法,大概会变成:
1 2 3 4 5 6
| User user = userService.findById(userId); String email = "no-email@example.com";
if (user != null && user.isActive()) { email = user.getEmail(); }
|
传统写法也没错,但 Optional 的链式写法更适合表达“可能中断的值转换流程”。
三、Optional 的基本使用方法 Basic usage of Optional
1. 创建 Optional
Optional.empty()
创建一个空的 Optional:
1
| Optional<User> emptyUser = Optional.empty();
|
注意:不要用 == 判断某个 Optional 是否等于 Optional.empty()。官方文档说明,不能依赖 Optional.empty() 是单例对象,应使用 isPresent() 或 isEmpty() 判断。
1 2 3 4 5 6 7 8 9
| if (optional == Optional.empty()) { }
if (optional.isEmpty()) { }
|
isEmpty() 是 Java 11 引入的。如果你还在 Java 8,可以使用:
1 2 3
| if (!optional.isPresent()) { }
|
Optional.of(value)
创建一个一定有值的 Optional:
1
| Optional<String> name = Optional.of("Mario");
|
如果传入 null,会直接抛出 NullPointerException:
1
| Optional<String> name = Optional.of(null);
|
所以 Optional.of() 适合用于你非常确定值不为 null 的场景。
Optional.ofNullable(value)
当值可能为 null 时,使用 ofNullable():
1 2
| String name = getNameFromDatabase(); Optional<String> optionalName = Optional.ofNullable(name);
|
如果 name 不为 null,得到一个有值的 Optional;如果为 null,得到 Optional.empty()。
这也是把旧代码中的可空返回值转换为 Optional 的常用方式。
2. 判断是否有值
1 2 3 4 5 6
| Optional<User> optionalUser = userService.findById(userId);
if (optionalUser.isPresent()) { User user = optionalUser.get(); System.out.println(user.getName()); }
|
这段代码可以运行,但它不是最推荐的 Optional 写法。因为 isPresent() + get() 本质上只是把 user != null 换了一身新衣服,业务逻辑复杂后还是容易变得啰嗦。
更推荐使用 ifPresent()、map()、orElse()、orElseThrow() 等方法。
3. ifPresent()
有值时执行逻辑,无值时什么都不做:
1 2
| userService.findById(userId) .ifPresent(user -> log.info("found user: {}", user.getName()));
|
适合处理“有就做,没有就算了”的场景。
4. ifPresentOrElse()
Java 9 引入 ifPresentOrElse(),可以同时处理有值和无值:
1 2 3 4 5
| userService.findById(userId) .ifPresentOrElse( user -> log.info("found user: {}", user.getName()), () -> log.warn("user not found, userId={}", userId) );
|
如果项目还在 Java 8,可以继续用普通 if,不用为了链式写法强行绕。
5. orElse()
无值时返回默认值:
1 2 3
| String username = userService.findById(userId) .map(User::getName) .orElse("anonymous");
|
orElse() 的参数会先被计算出来,再传给方法。所以如果默认值创建成本较高,或者带有副作用,就要小心。
1
| String username = optionalName.orElse(createDefaultName());
|
即使 optionalName 有值,createDefaultName() 也会先执行。
6. orElseGet()
无值时再通过 Supplier 延迟获取默认值:
1
| String username = optionalName.orElseGet(() -> createDefaultName());
|
如果 optionalName 有值,createDefaultName() 不会执行。
所以经验上可以这么选:
| 场景 |
推荐写法 |
| 默认值是简单常量 |
orElse("anonymous") |
| 默认值需要查询、计算、创建对象 |
orElseGet(() -> loadDefault()) |
| 默认值方法有日志、数据库调用、远程调用等副作用 |
orElseGet(...) |
7. orElseThrow()
无值时抛出异常:
1 2
| User user = userService.findById(userId) .orElseThrow(() -> new BusinessException("用户不存在"));
|
这很适合“查不到就是错误”的场景,例如更新用户资料前必须先找到用户:
1 2 3 4 5 6 7
| public void updateUsername(Long userId, String username) { User user = userRepository.findById(userId) .orElseThrow(() -> new BusinessException("用户不存在"));
user.setUsername(username); userRepository.save(user); }
|
Java 10 开始,orElseThrow() 可以不传异常 Supplier,默认抛 NoSuchElementException:
1
| User user = optionalUser.orElseThrow();
|
不过在业务系统中,一般更建议抛出带明确业务信息的异常。
8. map()
map() 用来把 Optional 中的值转换成另一个值:
1 2
| Optional<String> username = userService.findById(userId) .map(User::getName);
|
如果用户存在,则返回 Optional<String>;如果用户不存在,则返回 Optional.empty()。
也可以继续接默认值:
1 2 3
| String username = userService.findById(userId) .map(User::getName) .orElse("anonymous");
|
map() 的一个重要特点是:如果映射函数返回 null,结果会变成 Optional.empty()。
1 2
| Optional<String> email = userService.findById(userId) .map(User::getEmail);
|
如果 getEmail() 返回 null,最终就是空 Optional。
9. flatMap()
如果转换函数本身已经返回 Optional,就使用 flatMap(),避免出现 Optional<Optional<T>>。
错误示例:
1 2
| Optional<Optional<Address>> address = userService.findById(userId) .map(user -> addressService.findDefaultAddress(user.getId()));
|
推荐写法:
1 2
| Optional<Address> address = userService.findById(userId) .flatMap(user -> addressService.findDefaultAddress(user.getId()));
|
可以这样理解:
map():普通值转换。
flatMap():转换函数本身返回 Optional 时使用。
10. filter()
filter() 用来在 Optional 有值时继续判断条件,不满足就变成空:
1 2
| Optional<User> activeUser = userService.findById(userId) .filter(User::isActive);
|
实际业务例子:
1 2 3 4 5 6
| public Optional<Coupon> findUsableCoupon(Long couponId) { return couponRepository.findById(couponId) .filter(Coupon::isEnabled) .filter(coupon -> !coupon.isExpired()) .filter(coupon -> coupon.getRemainCount() > 0); }
|
这段代码表达很清楚:
- 优惠券存在。
- 优惠券启用。
- 优惠券未过期。
- 优惠券还有库存。
任何条件不满足,都返回空 Optional。
11. or()
Java 9 引入 or(),用于在当前 Optional 为空时,返回另一个 Optional:
1 2
| Optional<User> user = findFromCache(userId) .or(() -> findFromDatabase(userId));
|
它适合表达多级查找:
1 2 3
| Optional<Config> config = findFromUserConfig(key) .or(() -> findFromTenantConfig(key)) .or(() -> findFromGlobalConfig(key));
|
12. stream()
Java 9 引入 Optional.stream(),可以把 Optional 转成 0 个或 1 个元素的 Stream。
例如,现在有一个 List<Optional<User>>:
1 2 3 4 5
| List<Optional<User>> optionalUsers = List.of( Optional.of(new User("Mario")), Optional.empty(), Optional.of(new User("Luigi")) );
|
可以这样提取出真正存在的用户:
1 2 3
| List<User> users = optionalUsers.stream() .flatMap(Optional::stream) .toList();
|
在 Java 8 中,可以用这种写法:
1 2 3 4
| List<User> users = optionalUsers.stream() .filter(Optional::isPresent) .map(Optional::get) .collect(Collectors.toList());
|
四、Optional 的最佳实践 Best Practices for Optional
1. 优先用于方法返回值
Optional 最适合的位置是方法返回值,尤其是查询、查找、匹配、解析这类“可能没有结果”的方法。
推荐:
1 2 3
| public Optional<User> findById(Long userId) { return Optional.ofNullable(userMapper.selectById(userId)); }
|
不推荐:
1 2 3
| public User findById(Long userId) { return userMapper.selectById(userId); }
|
命名上也建议区分语义:
| 方法名 |
建议返回值 |
语义 |
findById |
Optional<User> |
找不到是正常情况 |
getById |
User |
语义上更像必须拿到,拿不到可抛异常 |
existsById |
boolean |
只关心是否存在 |
listByUserId |
List<Order> |
集合为空即可,不需要 Optional 包集合 |
2. Optional 变量本身不要为 null
官方文档明确说明:类型为 Optional 的变量不应该为 null,它应该始终指向一个 Optional 实例。
不推荐:
1
| Optional<User> optionalUser = null;
|
推荐:
1
| Optional<User> optionalUser = Optional.empty();
|
如果 Optional 本身还能为 null,那就会出现一种非常尴尬的情况:
1 2 3
| if (optionalUser != null && optionalUser.isPresent()) { }
|
这就等于把 Optional 的价值吃回去了,属于“买了安全带但放后备箱里”的写法。
3. 不要无脑使用 get()
get() 在 Optional 为空时会抛出 NoSuchElementException。如果你写的是:
1
| User user = optionalUser.get();
|
那你必须非常确定它一定有值。否则这和直接空指针没本质区别,只是换了一个异常。
不推荐:
1
| User user = userService.findById(userId).get();
|
推荐:
1 2
| User user = userService.findById(userId) .orElseThrow(() -> new BusinessException("用户不存在"));
|
或者:
1 2 3
| String username = userService.findById(userId) .map(User::getName) .orElse("anonymous");
|
4. 不要把 Optional 当成集合
Optional 最多只有一个值,它不是集合,也不是 List 的替代品。
如果结果可能有多个,用集合:
1 2 3
| public List<Order> listOrders(Long userId) { return orderRepository.listByUserId(userId); }
|
如果没有订单,返回空集合即可:
1
| return Collections.emptyList();
|
不推荐:
1 2 3
| public Optional<List<Order>> listOrders(Long userId) { return Optional.ofNullable(orderRepository.listByUserId(userId)); }
|
Optional<List<T>> 会让调用方同时处理两层含义:
- Optional 是否为空。
- List 是否为空。
这通常是多余复杂度。
5. 根据业务语义选择 Optional、异常或空集合
不是所有“没有值”都应该用 Optional。
| 场景 |
推荐返回 |
| 根据 ID 查询,找不到是正常情况 |
Optional<T> |
| 根据 ID 获取,找不到是业务错误 |
抛业务异常或返回 T |
| 查询列表,无数据 |
空集合 |
| 判断是否存在 |
boolean |
| 计算结果可能不存在 |
Optional<T> |
| 参数非法 |
抛异常 |
例子:
1 2 3 4 5 6 7 8 9 10 11 12
| public Optional<User> findById(Long userId) { return userRepository.findById(userId); }
public User getById(Long userId) { return userRepository.findById(userId) .orElseThrow(() -> new BusinessException("用户不存在")); }
public List<Order> listOrders(Long userId) { return orderRepository.listByUserId(userId); }
|
这样 API 语义更干净。
6. 默认值创建成本高时使用 orElseGet()
看这段代码:
1
| User user = optionalUser.orElse(createGuestUser());
|
即使 optionalUser 有值,createGuestUser() 也会先执行。
如果创建默认用户很轻量,问题不大。但如果里面有数据库查询、远程调用、日志埋点、对象构建等逻辑,就会造成不必要的开销或副作用。
推荐:
1
| User user = optionalUser.orElseGet(() -> createGuestUser());
|
或者更简洁:
1
| User user = optionalUser.orElseGet(this::createGuestUser);
|
7. 使用 map / flatMap 处理多层取值
传统多层判空:
1 2 3 4 5 6 7
| String city = null;
if (user != null && user.getProfile() != null && user.getProfile().getAddress() != null) { city = user.getProfile().getAddress().getCity(); }
|
Optional 写法:
1 2 3 4 5
| String city = Optional.ofNullable(user) .map(User::getProfile) .map(Profile::getAddress) .map(Address::getCity) .orElse("Unknown");
|
这类写法适合读取链路比较清楚的值。不过也别过度链式化,如果业务逻辑有很多分支、日志、异常、状态变更,普通 if 往往更易读。
8. 对基础类型优先考虑 OptionalInt / OptionalLong / OptionalDouble
如果要表达基础类型的可选值,可以使用:
OptionalInt
OptionalLong
OptionalDouble
例如:
1 2 3 4 5
| public OptionalInt findMaxAge(List<User> users) { return users.stream() .mapToInt(User::getAge) .max(); }
|
相比 Optional<Integer>,OptionalInt 可以避免装箱拆箱,更贴合基础类型场景。
9. 在 Stream 中善用 Optional.stream()
Java 9+ 可以这样把多个 Optional 展平:
1 2 3 4
| List<User> users = userIds.stream() .map(userService::findById) .flatMap(Optional::stream) .toList();
|
这比下面这种写法更清爽:
1 2 3 4 5
| List<User> users = userIds.stream() .map(userService::findById) .filter(Optional::isPresent) .map(Optional::get) .toList();
|
10. 不要为了“看起来高级”而牺牲可读性
Optional 不是比赛谁能把代码写成一行。
不推荐:
1 2 3 4 5 6 7
| return Optional.ofNullable(request) .map(Request::getUser) .filter(User::isActive) .map(User::getRole) .filter(role -> role.hasPermission("ORDER_CREATE")) .map(role -> orderService.create(request)) .orElseThrow(() -> new BusinessException("无权限或请求非法"));
|
这段代码的问题是:不同失败原因被混在一起了。
更清晰的写法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| if (request == null) { throw new BusinessException("请求不能为空"); }
User user = request.getUser(); if (user == null || !user.isActive()) { throw new BusinessException("用户无效"); }
Role role = user.getRole(); if (role == null || !role.hasPermission("ORDER_CREATE")) { throw new BusinessException("无下单权限"); }
return orderService.create(request);
|
结论很简单:Optional 适合处理值转换,不适合隐藏复杂业务分支。
五、不合理的使用场景 Inappropriate Use Cases
1. 不建议用作方法参数
不推荐:
1 2 3
| public void sendEmail(Optional<String> email) { email.ifPresent(this::doSendEmail); }
|
调用方会很别扭:
1
| sendEmail(Optional.ofNullable(user.getEmail()));
|
更直接的方式:
1 2 3 4 5 6
| public void sendEmail(String email) { if (email == null || email.isBlank()) { return; } doSendEmail(email); }
|
或者把语义拆开:
1 2 3 4 5
| public void sendEmail(User user) { if (user.hasEmail()) { doSendEmail(user.getEmail()); } }
|
为什么不建议用作参数?
- 调用方负担变重。
- 并不能阻止传入
null,因为别人仍然可以调用 sendEmail(null)。
- 参数是否可选,通常可以通过方法重载、默认值、构建器、配置对象表达。
2. 不建议用作实体类字段
不推荐:
1 2 3 4
| public class User { private Long id; private Optional<String> nickname; }
|
更推荐:
1 2 3 4
| public class User { private Long id; private String nickname; }
|
原因:
- Optional 官方定位主要是方法返回值,而不是字段。
- Optional 是值基类,不应该依赖对象身份,也不适合做同步锁。
- ORM、JSON 序列化、Bean 映射等框架处理 Optional 字段时可能需要额外适配。
- 字段是数据模型的一部分,而 Optional 更像 API 边界上的语义提示。
如果你想表达字段可为空,数据库、校验注解、文档和类型约定更合适:
1 2 3 4 5 6 7 8
| public class User { private Long id;
private String nickname; }
|
或者使用 Bean Validation:
1 2 3 4 5 6
| public class CreateUserRequest { @NotBlank private String username;
private String nickname; }
|
3. 不建议用在 DTO / VO / RPC 入参出参字段中
不推荐:
1 2 3 4
| public class UserDTO { private Long id; private Optional<String> nickname; }
|
在接口对象里使用 Optional 字段,很容易引发几个问题:
- 前端看到的 JSON 结构可能不符合预期。
- OpenAPI / Swagger / Apifox 文档可能变得奇怪。
- RPC、序列化框架可能不按你想的方式处理。
- 字段是否存在、字段是否为 null、Optional 是否 empty,语义容易混在一起。
接口对象更建议保持朴素:
1 2 3 4
| public class UserDTO { private Long id; private String nickname; }
|
如果需要表达字段是否必填,使用文档、注解或协议层能力:
1 2 3 4 5 6
| public class UpdateUserRequest { @NotNull private Long id;
private String nickname; }
|
4. 不建议返回 Optional 集合
不推荐:
1 2 3
| public Optional<List<Order>> findOrders(Long userId) { return Optional.ofNullable(orderRepository.listByUserId(userId)); }
|
推荐:
1 2 3 4
| public List<Order> listOrders(Long userId) { List<Order> orders = orderRepository.listByUserId(userId); return orders == null ? Collections.emptyList() : orders; }
|
集合本身已经可以表达“没有数据”:
List 为空:查到了 0 条。
Map 为空:没有键值。
Set 为空:没有元素。
没必要再包一层 Optional。
5. 不建议滥用 isPresent() + get()
不推荐:
1 2 3 4 5 6 7 8
| Optional<User> optionalUser = userService.findById(userId);
if (optionalUser.isPresent()) { User user = optionalUser.get(); return user.getName(); }
return "anonymous";
|
推荐:
1 2 3
| return userService.findById(userId) .map(User::getName) .orElse("anonymous");
|
当然,不是说 isPresent() 永远不能用。如果逻辑非常复杂,普通 if 可能更清楚:
1 2 3 4 5 6 7 8 9 10
| Optional<User> optionalUser = userService.findById(userId);
if (optionalUser.isEmpty()) { log.warn("user not found, userId={}", userId); return; }
User user = optionalUser.get(); auditLog(user); sendNotification(user);
|
这里 isEmpty() 加提前返回就很直观。最佳实践不是“禁用 if”,而是别把 Optional 写回旧式判空的老路。
6. 不建议用 Optional 包装每一个可能为 null 的局部变量
不推荐:
1 2 3
| Optional<String> name = Optional.ofNullable(user.getName()); Optional<String> email = Optional.ofNullable(user.getEmail()); Optional<String> phone = Optional.ofNullable(user.getPhone());
|
这会让代码到处都是包装和拆包,读起来反而更重。
局部变量能用普通判空讲清楚时,就用普通判空:
1 2 3 4 5 6
| String email = user.getEmail(); if (email == null || email.isBlank()) { return; }
sendEmail(email);
|
Optional 更适合出现在方法边界上,而不是每一行内部实现里。
7. 不建议把 Optional 当作异常替代品
Optional 表达的是“没有结果”,不是“程序出错”。
推荐使用 Optional:
1 2 3
| public Optional<User> findByEmail(String email) { return userRepository.findByEmail(email); }
|
不推荐使用 Optional:
1 2 3 4 5 6
| public Optional<Order> createOrder(CreateOrderCommand command) { if (command == null) { return Optional.empty(); } }
|
command == null 是参数非法,不是“没有订单结果”。这种情况应该抛异常:
1 2 3 4 5 6
| public Order createOrder(CreateOrderCommand command) { if (command == null) { throw new IllegalArgumentException("command must not be null"); } }
|
判断标准:
- 没查到、没匹配到、没配置:可以考虑 Optional。
- 参数非法、状态错误、数据不一致、调用失败:应该考虑异常。
8. 不建议在性能敏感路径中过度使用 Optional
Optional 是对象包装。大多数业务系统里,这点开销可以忽略。但在高频循环、底层工具、性能敏感代码里,过度创建 Optional 可能没有必要。
不推荐:
1 2 3 4
| for (int i = 0; i < values.length; i++) { Optional<Integer> value = Optional.of(values[i]); sum += value.orElse(0); }
|
推荐:
1 2 3
| for (int value : values) { sum += value; }
|
在业务层,先考虑可读性;在底层热点代码里,再考虑对象分配和性能。
9. 不建议对 Optional 做身份相关操作
Optional 是值基类,官方文档提醒不应依赖对象身份,不要对 Optional 实例做同步。
不推荐:
1 2 3
| synchronized (optionalUser) { }
|
也不推荐:
1 2 3
| if (optionalUser == Optional.empty()) { }
|
推荐:
1 2 3
| if (optionalUser.isEmpty()) { }
|
或者 Java 8:
1 2 3
| if (!optionalUser.isPresent()) { }
|
六、常见业务写法示例
示例 1:查询用户,找不到返回默认展示名
1 2 3 4 5 6
| public String getDisplayName(Long userId) { return userRepository.findById(userId) .map(User::getNickname) .filter(nickname -> !nickname.isBlank()) .orElse("匿名用户"); }
|
示例 2:更新数据,找不到直接抛业务异常
1 2 3 4 5 6 7
| public void disableUser(Long userId) { User user = userRepository.findById(userId) .orElseThrow(() -> new BusinessException("用户不存在"));
user.disable(); userRepository.save(user); }
|
示例 3:缓存未命中时查数据库
Java 9+:
1 2 3 4
| public Optional<User> findUser(Long userId) { return findFromCache(userId) .or(() -> findFromDatabase(userId)); }
|
Java 8:
1 2 3 4 5 6 7
| public Optional<User> findUser(Long userId) { Optional<User> cachedUser = findFromCache(userId); if (cachedUser.isPresent()) { return cachedUser; } return findFromDatabase(userId); }
|
示例 4:多级属性安全读取
1 2 3 4 5 6 7
| public String getCity(User user) { return Optional.ofNullable(user) .map(User::getProfile) .map(Profile::getAddress) .map(Address::getCity) .orElse("Unknown"); }
|
示例 5:Stream 中过滤不存在的数据
1 2 3 4 5 6
| public List<User> findExistingUsers(List<Long> userIds) { return userIds.stream() .map(userRepository::findById) .flatMap(Optional::stream) .toList(); }
|
如果是 Java 8:
1 2 3 4 5 6 7
| public List<User> findExistingUsers(List<Long> userIds) { return userIds.stream() .map(userRepository::findById) .filter(Optional::isPresent) .map(Optional::get) .collect(Collectors.toList()); }
|
七、Optional 使用决策表
| 问题 |
建议 |
| 方法可能查不到单个对象? |
返回 Optional<T> |
| 方法查询列表但可能无数据? |
返回空集合,不要返回 Optional<List<T>> |
| 参数可选? |
优先考虑重载、Builder、配置对象,不建议用 Optional 参数 |
| 字段可为空? |
字段保持原始类型,用注解、文档、数据库约束表达 |
| 查不到是业务错误? |
orElseThrow() 抛业务异常 |
| 默认值创建很便宜? |
orElse(defaultValue) |
| 默认值创建很贵或有副作用? |
orElseGet(supplier) |
| 转换普通值? |
map() |
| 转换函数返回 Optional? |
flatMap() |
| 基础类型可选? |
OptionalInt、OptionalLong、OptionalDouble |
| Optional 自身是否可以为 null? |
不可以,使用 Optional.empty() |
八、一句话总结
Optional 的核心价值不是“消灭 null”,而是让可能缺失的返回值变得显式、可读、可组合。
写 Optional 时记住三句话:
- 优先用于方法返回值。
- 不要让 Optional 自己变成 null。
- 不要为了链式而链式,可读性永远排第一。
Optional 用得好,代码会更清楚;用得过头,代码会更绕。它是一把小刀,不是瑞士军刀,更不是 Java 世界的全自动扫雷器。
参考资料