这两个东西其实是配套用的:Stream 负责"批量处理集合",Optional 负责"处理可能没有的值"。
先看一张速查表
| 你遇到的情况 | 用谁 | 典型写法 |
|---|---|---|
| 集合过滤、转换、统计 | Stream |
list.stream().filter(...).map(...).toList() |
| 查询结果可能为空 | Optional |
findById(id).orElseThrow() |
| 查找第一个/最大值 | Stream + Optional |
stream().findFirst().orElse(null) |
| 深层对象取值,可能 null | Optional.flatMap |
ofNullable(u).flatMap(User::getAddress).map(Address::getCity) |
| 集合里有空值 | Optional.stream() |
ids.stream().map(repo::findById).flatMap(Optional::stream) |
1. Optional:专门处理"可能没有"
Optional 不是"万能包装类",它主要用在方法返回值上,告诉调用者:"这个结果可能没有,你要处理。"
java
Optional<User> opt = userRepository.findById(id);
常见处理:
java
// 有值就返回,没有就抛异常
User user = opt.orElseThrow(() -> new RuntimeException("用户不存在"));
// 没有就返回默认值
String name = opt.map(User::getName).orElse("匿名用户");
// 有值才执行
opt.ifPresent(u -> System.out.println("找到用户:" + u.getName()));
// 多级取值,避免 NPE
String city = Optional.ofNullable(user)
.flatMap(u -> Optional.ofNullable(u.getAddress()))
.map(Address::getCity)
.orElse("未知城市");
注意几个点:
- 确定有值 :
Optional.of(value) - 可能为 null :
Optional.ofNullable(value) - 明确没有值 :
Optional.empty() - 尽量不要直接
get(),容易NoSuchElementException。 - Optional 一般不做字段、参数,主要做返回值。
2. Stream:集合的流水线处理
Stream 就是把集合变成一条流水线:过滤、转换、分组、统计。
java
List<String> names = users.stream()
.filter(u -> u.getAge() >= 18)
.map(User::getName)
.toList(); // JDK 16+
常见操作:
java
// 过滤
List<User> adults = users.stream()
.filter(u -> u.getAge() >= 18)
.toList();
// 转换
List<String> emails = users.stream()
.map(User::getEmail)
.filter(Objects::nonNull)
.toList();
// 查找
Optional<User> first = users.stream()
.filter(u -> "张三".equals(u.getName()))
.findFirst();
// 统计
long count = users.stream()
.filter(u -> u.getAge() > 18)
.count();
// 分组
Map<String, List<User>> byDept = users.stream()
.collect(Collectors.groupingBy(User::getDept));
JDK 17 里可以直接用:
java
list.stream().map(User::getName).toList();
比以前的 collect(Collectors.toList()) 更短,而且结果是不可变列表。
3. Stream + Optional:经常一起出现
很多 Stream 操作本身就会返回 Optional,比如:
findFirst()findAny()max()min()reduce()
java
Optional<User> user = users.stream()
.filter(u -> u.getAge() > 18)
.findFirst();
User result = user.orElseThrow(() -> new RuntimeException("没有符合条件的用户"));
再比如批量查询时,有些 ID 查不到:
java
List<User> users = ids.stream()
.map(userRepository::findById) // Stream<Optional<User>>
.flatMap(Optional::stream) // Stream<User>,空的自动丢掉
.toList();
这里 Optional::stream 很关键:有值就变成 1 个元素,没值就变成 0 个元素。
4. 一个比较完整的业务示例
java
public class OrderService {
private final List<Order> orders;
public OrderService(List<Order> orders) {
this.orders = orders;
}
/** 根据订单号查询,可能不存在 */
public Optional<Order> findOrder(String orderNo) {
return orders.stream()
.filter(o -> o.getOrderNo().equals(orderNo))
.findFirst();
}
/** 获取买家名称,可能没有订单 */
public String getBuyerName(String orderNo) {
return findOrder(orderNo)
.map(Order::getUser)
.map(User::getName)
.orElse("未知买家");
}
/** 获取已支付金额,没有订单或没支付则返回 0 */
public BigDecimal getPaidAmount(String orderNo) {
return findOrder(orderNo)
.filter(Order::isPaid)
.map(Order::getAmount)
.orElse(BigDecimal.ZERO);
}
/** 统计所有已支付订单总金额 */
public BigDecimal totalPaidAmount() {
return orders.stream()
.filter(Order::isPaid)
.map(Order::getAmount)
.reduce(BigDecimal.ZERO, BigDecimal::add);
}
/** 按用户分组,统计每人订单数 */
public Map<String, Long> countByUser() {
return orders.stream()
.collect(Collectors.groupingBy(
o -> o.getUser().getName(),
Collectors.counting()));
}
}
5. 最容易踩的坑
- 不要写
optional.get()裸取 ,优先用orElse / orElseGet / orElseThrow。 - 不要为了优雅强行用 Optional ,简单
if (x != null)有时更清楚。 orElse会先执行 ,如果默认值计算代价大,用orElseGet。- Optional 不要做实体字段,尤其 JPA、Redis、序列化对象里容易出问题。
- Stream 不能复用,终端操作执行完就消耗了。
- 业务代码里少用并行流,涉及数据库、RPC、共享状态时容易出问题。
一句话记
Stream 处理"一堆数据",Optional 处理"可能没有的一个数据"。
Stream 结尾经常返回 Optional,Optional 也可以转成 Stream,两者配合起来就能少写很多
if (x != null)。
如果你愿意,可以把你现在项目里一段带 if/for/null 判断的代码贴出来,我可以帮你改写成 Optional + Stream 版本。