【WebFlux】第三篇 —— 常用操作符与数据流转换

操作符:响应式流的"加工车间"

在前面的笔记中,我们学会了如何创建 Mono 和 Flux 数据流。但在真实的业务场景中,数据往往需要经过清洗、转换、过滤或聚合。Reactor 提供了数百个操作符(Operators)来完成这些工作。你可以把操作符想象成工厂流水线上的各种加工机器,它们接收上游的数据流,经过处理后,向下游输出新的数据流。

本篇笔记将带你掌握最核心、最常用的几类操作符,让你能够熟练地对数据流进行加工。

转换操作符:map 与 flatMap 的核心对决

这是响应式编程中最重要,也是最容易混淆的一对操作符。

  • map(同步 1对1 转换) :map 用于对流中的每个元素执行一个同步的转换函数。它的输入是一个元素,输出也是一个元素,数据流的数量保持不变。
    代码示例:将字符串列表转换为它们的长度。

    java 复制代码
    Flux.just("Java", "Spring", "WebFlux")
        .map(String::length) // 同步转换:字符串 -> 长度
        .subscribe(length -> System.out.println("长度: " + length));
    // 输出: 4, 6, 7
  • flatMap(异步 1对N 转换) :flatMap 用于将一个元素转换为一个新的异步数据流(Publisher),然后将这些子流扁平化(Flatten)合并成一个新流。它的输入是一个元素,输出是一个流。
    代码示例:将每个单词拆分成单个字符。

    java 复制代码
    Flux.just("Hi", "OK")
        .flatMap(word -> Flux.fromArray(word.split(""))) // 1个单词 -> 包含多个字符的子流
        .subscribe(System.out::println);
    // 输出: H, I, O, K (注意:flatMap不保证子流间的绝对顺序,因为它是并发处理的)
  • concatMap(保证顺序的异步转换):如果你既需要 flatMap 的异步转换能力,又需要保证严格的顺序,可以使用 concatMap。它会等待前一个子流完全处理完毕后,才开始处理下一个子流。

过滤操作符:精准挑选数据

当数据流中混杂了不需要的元素时,过滤操作符就派上用场了。

  • filter(条件过滤):只保留满足条件的元素。

    java 复制代码
    Flux.range(1, 10)
        .filter(num -> num % 2 == 0) // 过滤出偶数
        .subscribe(even -> System.out.println("偶数: " + even));
    // 输出: 2, 4, 6, 8, 10
  • distinct(去重):自动过滤掉流中重复的元素。

    java 复制代码
    Flux.just(1, 2, 2, 3, 3, 4)
        .distinct()
        .subscribe(System.out::println); // 输出: 1, 2, 3, 4
  • take 与 skip(截取与跳过):take(n) 只获取前 n 个元素(拿完即关水龙头);skip(n) 则跳过前 n 个元素。

    java 复制代码
    Flux.just("A", "B", "C", "D", "E")
        .skip(2)      // 跳过前2个
        .take(2)      // 只取接下来的2个
        .subscribe(System.out::println); // 输出: C, D

组合操作符:拼接多根水管

在实际开发中,我们经常需要把多个数据流合并成一个。Reactor 提供了两种截然不同的组合方式:

  • merge / mergeWith(交错合并) :将多个流合并为一个,谁先产生数据,谁就先往下游流。

    java 复制代码
    Flux<String> flux1 = Flux.just("A1", "A2").delayElements(Duration.ofMillis(100));
    Flux<String> flux2 = Flux.just("B1", "B2");
    
    Flux.merge(flux1, flux2).subscribe(System.out::println);
    // 输出可能是: B1, B2, A1, A2 (因为flux2没有延迟,会先发射)
  • concat / concatWith(首尾相接) :严格按照顺序,等第一个流完全结束后,才开始流第二个流的数据。

    java 复制代码
    Flux.concat(flux1, flux2).subscribe(System.out::println);
    // 输出严格为: A1, A2, B1, B2
  • zip / zipWith(配对组合) :像"相亲"一样,把两个流中相同位置的元素配对组合成一个元组(Tuple)。

    java 复制代码
    Flux.zip(Flux.just("张三", "李四"), Flux.just(18, 25))
        .subscribe(tuple -> System.out.println(tuple.getT1() + " 年龄: " + tuple.getT2()));
    // 输出: 张三 年龄: 18, 李四 年龄: 25

聚合操作符:将流收敛为单值

有时候我们需要对整个流进行计算,最终只输出一个结果。

  • reduce(聚合计算) :

    java 复制代码
    Flux.range(1, 5)
        .reduce((a, b) -> a + b) // 计算 1+2+3+4+5
        .subscribe(sum -> System.out.println("总和: " + sum)); // 输出: 15
  • collectList(收集为集合) :将流中的所有元素收集到一个 List 中,返回一个 Mono<List>。这在需要将响应式结果传递给不支持响应式的传统代码时非常有用。

    java 复制代码
    Flux.just("Apple", "Banana")
        .collectList()
        .subscribe(list -> System.out.println("收集结果: " + list)); // 输出: [Apple, Banana]

本篇小结:掌握 map、flatMap、filter 和 merge/concat 是响应式编程的必修课。记住一个核心原则:如果转换结果是一个普通对象,用 map;如果转换结果是一个新的 Mono/Flux,用 flatMap。

下一步预告:数据流在流转过程中难免会遇到异常(如网络超时、数据库报错)。在响应式编程中,传统的 try-catch 已经失效。下一篇笔记,我们将深入探讨响应式流的异常处理机制,并学习如何编写优雅的响应式单元测试。

相关推荐
灯澜忆梦16 小时前
【面向对象编程C++】| 基础语法
java·c++·算法
一个风轻云淡16 小时前
GCC 和 GDB命令简单解读
java·linux·前端
省钱兄--zs16 小时前
西安24小时自助健身房解决方案实战:系统开发与部署全流程指南
java·spring boot·系统架构·intellij-idea·需求分析
资料库0117 小时前
Linux 8个常见运维故障排查
java·linux·运维
要开心吖ZSH17 小时前
MySQL 慢 SQL 排查操作手册-个人笔记版
java·笔记·sql·mysql·慢查询
Joy T18 小时前
Spring AI 接入已有 Java 项目的三种架构设计
java·人工智能·springai·ai入门·chatclient·ai service·ai能力接入
杨运交18 小时前
[076][核心模块]构建优雅的Java异常处理框架:从错误码到全局异常处理
java·开发语言
caoerzhong18 小时前
仓库数字化成熟度分级:JeeWMS 开源 Java 仓库管理系统如何支撑从可见到自治的四级跃迁
java·开源
MayBaymax18 小时前
RocketMQ 存储机制
java·rocketmq
程序猿_极客19 小时前
【免费】分享一套优质的基于SpringBoot的服装商城管理系统的设计与实现(带可视化图表、协同过滤功能),源码+文档+视频详解(讲解)
java·spring boot·后端·服装商城管理系统