1. 概述

本文将深入探讨 java.util.stream.Stream API,重点介绍如何利用该特性处理无限数据流。这种操作的核心在于 Stream API 的惰性求值机制。

Stream 的惰性特性通过两种操作类型的分离实现:中间操作终端操作。理解这两者的区别是掌握无限流的关键。

2. 中间操作与终端操作

所有 Stream 操作分为中间操作和终端操作,它们组合形成完整的流处理管道。一个典型的流管道包含:

  • 数据源(如集合、数组、生成器函数、I/O 通道或无限序列生成器)
  • 零个或多个中间操作
  • 一个终端操作

2.1. 中间操作

中间操作在终端操作调用前不会实际执行。它们通过以下方法构建流管道:

  • filter()
  • map()
  • flatMap()
  • distinct()
  • sorted()
  • peek()
  • limit()
  • skip()

所有中间操作都是惰性的,它们不会立即处理数据,而是返回一个新的流。只有当终端操作触发时,整个管道才会开始执行。这个特性对无限流至关重要——它允许我们定义永不终止的数据流,但只在需要时才实际处理。

2.2. 终端操作

终端操作会触发整个流管道的执行,产生结果或副作用。执行后,流管道即被消耗,无法复用。终端操作通常是立即求值的,会完整遍历数据源。

处理无限流时必须特别注意:终端操作必须配合短路操作(如 limit())使用,否则程序将陷入死循环。常见终端操作包括:

  • forEach()
  • forEachOrdered()
  • toArray()
  • reduce()
  • collect()
  • min()
  • max()
  • count()
  • anyMatch()
  • allMatch()
  • noneMatch()
  • findFirst()
  • findAny()

⚠️ 踩坑提醒:忘记在无限流上使用 limit() 是最常见的死循环原因!

3. 无限流实战

理解了中间/终端操作的区别后,我们就能创建真正的无限流。下面生成一个从 0 开始、每次递增 2 的无限序列:

// 创建无限流
Stream<Integer> infiniteStream = Stream.iterate(0, i -> i + 2);

// 限制并收集结果
List<Integer> collect = infiniteStream
  .limit(10)  // 关键:必须限制元素数量!
  .collect(Collectors.toList());

// 验证结果
assertEquals(collect, Arrays.asList(0, 2, 4, 6, 8, 10, 12, 14, 16, 18));

通过 iterate() 创建无限流后,使用 limit() 进行截断。得益于 Stream 的惰性特性,实际只生成了前 10 个元素。

4. 自定义类型的无限流

现在创建一个无限随机 UUID 流。首先定义数据供应器:

Supplier<UUID> randomUUIDSupplier = UUID::randomUUID;

然后基于供应器生成无限流:

Stream<UUID> infiniteStreamOfRandomUUID = Stream.generate(randomUUIDSupplier);

使用时务必配合 limit()skip() 控制数据范围:

// 跳过前10个,取后续10个
List<UUID> randomUUIDs = infiniteStreamOfRandomUUID
  .skip(10)
  .limit(10)
  .collect(Collectors.toList());

技巧:任何自定义类型都能通过 Supplier 实现无限流生成。

5. 用 Stream 实现 Do-While 循环

传统 do-while 循环:

int i = 0;
while (i < 10) {
    System.out.println(i);
    i++;
}

用 Stream API 实现:

Stream<Integer> integers = Stream.iterate(0, i -> i + 1);
integers
  .limit(10)  // 替代循环条件
  .forEach(System.out::println);

虽然代码更简洁,但 limit() 的语义不如 doWhile() 直观。这是 Stream API 在循环控制上的一个小遗憾。

6. 总结

本文展示了如何利用 Java 8 Stream API 处理无限数据流。核心要点:

  1. 中间操作构建管道(惰性执行)
  2. 终端操作触发计算(立即执行)
  3. 无限流必须配合 limit() 等短路操作使用
  4. 通过 Supplier 可生成任意类型的无限流

这种模式在特定场景下(如模拟数据、算法测试)能显著简化代码。完整示例代码可在 GitHub 项目 中获取(Maven 项目,可直接导入运行)。


原始标题:Java 8 and Infinite Streams

« 上一篇: 如何测试RxJava?