Optional和Stream经典丰富的用法demo,以及相关的详细介绍

这两个东西其实是配套用的: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 版本。

相关推荐
wang_shu_mo_ran1 分钟前
MyBatis 从入门到进阶(一)
java·spring·mybatis
量化分析码农1 小时前
股票历史数据的复权参数对照:多级别返回的字段与数值差异
java·javascript·后端
猫猫不是喵喵.2 小时前
从0到1创建一个微服务项目
java·数据库·微服务·1024程序员节
瑞雪兆丰年兮2 小时前
JavaWeb-day13-SpringBootWeb事务管理&AOP
java·开发语言·springboot·javaweb·事务·web·aop
曲径海盗2 小时前
Try-Except怎么用,3个追问解答在自动化下的应用
java·jvm·oracle
Cosolar3 小时前
极简智库:快速搭建你的开源项目教程阅读平台,让 docs/ 目录被更多人看见
java·架构·github
nice_lcj5203 小时前
【从零理解 Spring AI · 16】Agent 与工作流:什么该写死,什么该让模型决定
java·人工智能·后端·spring ai·ai应用开发
北京云帆互联科技3 小时前
云帆培训考试系统更新说明:v9.3.0
java·学习
学长毕设5 小时前
基于SpringBoot的景点管理系统-附源码
java·spring boot·后端
sxlishaobin5 小时前
Maven Setting详解
java·学习·maven