操作符:响应式流的"加工车间"
在前面的笔记中,我们学会了如何创建 Mono 和 Flux 数据流。但在真实的业务场景中,数据往往需要经过清洗、转换、过滤或聚合。Reactor 提供了数百个操作符(Operators)来完成这些工作。你可以把操作符想象成工厂流水线上的各种加工机器,它们接收上游的数据流,经过处理后,向下游输出新的数据流。
本篇笔记将带你掌握最核心、最常用的几类操作符,让你能够熟练地对数据流进行加工。
转换操作符:map 与 flatMap 的核心对决
这是响应式编程中最重要,也是最容易混淆的一对操作符。
-
map(同步 1对1 转换) :map 用于对流中的每个元素执行一个同步的转换函数。它的输入是一个元素,输出也是一个元素,数据流的数量保持不变。
代码示例:将字符串列表转换为它们的长度。javaFlux.just("Java", "Spring", "WebFlux") .map(String::length) // 同步转换:字符串 -> 长度 .subscribe(length -> System.out.println("长度: " + length)); // 输出: 4, 6, 7 -
flatMap(异步 1对N 转换) :flatMap 用于将一个元素转换为一个新的异步数据流(Publisher),然后将这些子流扁平化(Flatten)合并成一个新流。它的输入是一个元素,输出是一个流。
代码示例:将每个单词拆分成单个字符。javaFlux.just("Hi", "OK") .flatMap(word -> Flux.fromArray(word.split(""))) // 1个单词 -> 包含多个字符的子流 .subscribe(System.out::println); // 输出: H, I, O, K (注意:flatMap不保证子流间的绝对顺序,因为它是并发处理的) -
concatMap(保证顺序的异步转换):如果你既需要 flatMap 的异步转换能力,又需要保证严格的顺序,可以使用 concatMap。它会等待前一个子流完全处理完毕后,才开始处理下一个子流。
过滤操作符:精准挑选数据
当数据流中混杂了不需要的元素时,过滤操作符就派上用场了。
-
filter(条件过滤):只保留满足条件的元素。
javaFlux.range(1, 10) .filter(num -> num % 2 == 0) // 过滤出偶数 .subscribe(even -> System.out.println("偶数: " + even)); // 输出: 2, 4, 6, 8, 10 -
distinct(去重):自动过滤掉流中重复的元素。
javaFlux.just(1, 2, 2, 3, 3, 4) .distinct() .subscribe(System.out::println); // 输出: 1, 2, 3, 4 -
take 与 skip(截取与跳过):take(n) 只获取前 n 个元素(拿完即关水龙头);skip(n) 则跳过前 n 个元素。
javaFlux.just("A", "B", "C", "D", "E") .skip(2) // 跳过前2个 .take(2) // 只取接下来的2个 .subscribe(System.out::println); // 输出: C, D
组合操作符:拼接多根水管
在实际开发中,我们经常需要把多个数据流合并成一个。Reactor 提供了两种截然不同的组合方式:
-
merge / mergeWith(交错合并) :将多个流合并为一个,谁先产生数据,谁就先往下游流。
javaFlux<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(首尾相接) :严格按照顺序,等第一个流完全结束后,才开始流第二个流的数据。
javaFlux.concat(flux1, flux2).subscribe(System.out::println); // 输出严格为: A1, A2, B1, B2 -
zip / zipWith(配对组合) :像"相亲"一样,把两个流中相同位置的元素配对组合成一个元组(Tuple)。
javaFlux.zip(Flux.just("张三", "李四"), Flux.just(18, 25)) .subscribe(tuple -> System.out.println(tuple.getT1() + " 年龄: " + tuple.getT2())); // 输出: 张三 年龄: 18, 李四 年龄: 25
聚合操作符:将流收敛为单值
有时候我们需要对整个流进行计算,最终只输出一个结果。
-
reduce(聚合计算) :
javaFlux.range(1, 5) .reduce((a, b) -> a + b) // 计算 1+2+3+4+5 .subscribe(sum -> System.out.println("总和: " + sum)); // 输出: 15 -
collectList(收集为集合) :将流中的所有元素收集到一个 List 中,返回一个 Mono<List>。这在需要将响应式结果传递给不支持响应式的传统代码时非常有用。
javaFlux.just("Apple", "Banana") .collectList() .subscribe(list -> System.out.println("收集结果: " + list)); // 输出: [Apple, Banana]
本篇小结:掌握 map、flatMap、filter 和 merge/concat 是响应式编程的必修课。记住一个核心原则:如果转换结果是一个普通对象,用 map;如果转换结果是一个新的 Mono/Flux,用 flatMap。
下一步预告:数据流在流转过程中难免会遇到异常(如网络超时、数据库报错)。在响应式编程中,传统的 try-catch 已经失效。下一篇笔记,我们将深入探讨响应式流的异常处理机制,并学习如何编写优雅的响应式单元测试。