1. 概述

RxJava 提供了丰富的操作符,用于将 Observable 发出的数据项转换为新的 Observable。其中,flatMapswitchMap 是两个非常常用但也容易混淆的操作符,尤其对于刚接触响应式编程的开发者来说,它们之间的差异常常让人摸不着头脑。

如果你还不熟悉 RxJava 的基本概念,建议先阅读 RxJava 入门指南

本文通过一个简单但贴近实际的场景,帮你彻底搞清楚 flatMapswitchMap 的核心区别,避免在项目中踩坑。

2. flatMap:扁平化合并,保留所有结果 ✅

flatMap 的作用是:将源 Observable 中的每个数据项,通过一个函数映射成一个新的 Observable,然后将这些 Observable 的结果“扁平化”合并到一个最终的 Observable 中,所有结果都会被保留。

关键点:

  • 所有中间 Observable 都会被订阅
  • 结果是所有子 Observable 发出的数据的合集
  • 发出顺序不保证与原始顺序一致(异步环境下常见)

场景示例:搜索建议(每输入一个字符都请求一次)

假设我们模拟一个搜索框的输入过程,用户每输入一个字符,就发起一次搜索请求。我们希望看到所有请求的结果都被返回,不管后续有没有新的输入。

为了简化,我们将输入序列定义为一个字符串列表:

// given
List<String> actualOutput = new ArrayList<>();
TestScheduler scheduler = new TestScheduler();
List<String> keywordToSearch = Arrays.asList("b", "bo", "boo", "book", "books");

// when
Observable.fromIterable(keywordToSearch)
  .flatMap(s -> Observable.just(s + " FirstResult", s + " SecondResult")
    .delay(10, TimeUnit.SECONDS, scheduler))
  .toList()
  .doOnSuccess(s -> actualOutput.addAll(s))
  .subscribe();

scheduler.advanceTimeBy(1, TimeUnit.MINUTES);

// then
assertThat(actualOutput, hasItems("b FirstResult", "b SecondResult",
  "boo FirstResult", "boo SecondResult",
  "bo FirstResult", "bo SecondResult",
  "book FirstResult", "book SecondResult",
  "books FirstResult", "books SecondResult"));

✅ 输出结果包含所有输入项对应的结果,共 10 条。

⚠️ 注意:由于 flatMap 是并发执行的(每个映射的 Observable 独立运行),最终输出顺序可能每次都不一样,RxJava 不保证顺序一致性。

3. switchMap:只保留最新结果,取消旧请求 ✅

switchMap 的行为与 flatMap 看似相似,但有一个致命区别

每当源 Observable 发出新数据时,switchMap 会取消前一个映射出的 Observable 的订阅,并只订阅最新的那个。

换句话说:只保留最新一次请求的结果,之前的请求即使还没完成,也会被丢弃。

场景示例:搜索优化(只关心最终关键词)

在实际搜索场景中,用户快速输入时,我们并不关心中间的 "b""bo" 这些短词的搜索结果,只希望拿到最终 "books" 的结果。这时,switchMap 就是最佳选择。

我们只需将上面例子中的 flatMap 替换为 switchMap

// given
List<String> actualOutput = new ArrayList<>();
TestScheduler scheduler = new TestScheduler();
List<String> keywordToSearch = Arrays.asList("b", "bo", "boo", "book", "books");

// when
Observable.fromIterable(keywordToSearch)
  .switchMap(s -> Observable.just(s + " FirstResult", s + " SecondResult")
    .delay(10, TimeUnit.SECONDS, scheduler))
  .toList()
  .doOnSuccess(s -> actualOutput.addAll(s))
  .subscribe();

scheduler.advanceTimeBy(1, TimeUnit.MINUTES);

// then
assertEquals(2, actualOutput.size());
assertThat(actualOutput, hasItems("books FirstResult", "books SecondResult"));

✅ 最终结果只有 2 条,且只包含 "books" 的搜索结果。

❌ 之前的 "b""bo" 等请求的结果全部被取消,不会出现在最终输出中。

💡 这正是 switchMap 的核心价值:避免处理过时数据,减少无效网络请求和 UI 更新,提升性能和用户体验。

4. 总结对比

特性 flatMap switchMap
是否保留所有结果 ✅ 是 ❌ 否,只保留最新
是否取消旧订阅 ❌ 否 ✅ 是,自动取消
适用场景 需要处理所有请求结果(如批量任务) 只关心最新状态(如搜索、防抖请求)
并发行为 所有子 Observable 并发执行 只执行最新的,旧的被中断
输出顺序 不保证 取决于最新 Observable 的顺序

简单粗暴记忆法:

  • flatMap“全都要” —— 所有请求都处理,结果全返回
  • switchMap“只认最新” —— 后到的请求会“踢掉”前面的

实际开发建议

  • 在搜索框、自动补全、实时查询等场景,优先使用 switchMap,避免 UI 闪烁和资源浪费
  • 在需要聚合多个独立请求结果的场景(如并行拉取多个配置),使用 flatMap
  • 如果你还担心顺序问题,可以考虑 concatMap(顺序执行,不并发)

所有示例代码已上传至 GitHub:https://github.com/eugenp/tutorials/tree/master/rxjava-modules/rxjava-core
建议 clone 下来跑一遍,印象更深刻。


原始标题:Difference Between Flatmap and Switchmap in Rxjava