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 版本。

相关推荐
小蒜学长1 小时前
基于过程性评价的ai助学平台(代码+数据库+LW)
java·spring boot·后端·微信小程序·过程性评价
洁洁!1 小时前
手搓 C++ 线程池详细讲解以及如何面试应答
java·c++·面试
萝卜白菜。1 小时前
TongWeb生产环境配置规范之三:优化TongWeb启动速度
java·开发语言
信誓旦旦的程序猿1 小时前
【Python量化实战宝典 #20】动量、反转择时手册:趋势跟随与均值回归(Python 可跑)
java·python·股票数据api·股票数据api接口·股票api数据接口·股票量化数据api·股票量化数据接口
电商全栈程序猿1 小时前
2026 开源商城系统选型指南:开发者实测对比Tigshop、ShopXO、CRMEB
java·运维·开源
骑着蜗牛撵大象3272 小时前
用 Codex 批量做数字人商品口播视频:从分镜 JSON 到成片校验的流水线
java·ffmpeg·json·音视频·数字人·视频·codex
博风2 小时前
使用Spring官网下载Springboot骨架
java·spring boot·spring
金銀銅鐵2 小时前
[Java] 用 CLI 展示 EnumSet 增减元素时的内部变化(上)
java·后端·ai编程
qq_436962182 小时前
金蝶云星空数据分析怎么做?
java·数据库·数据分析