271. Java Stream API - 理解 Java Stream 的流水线模型:中间操作 vs 终端操作

29 阅读2分钟

271. Java Stream API - 理解 Java Stream 的流水线模型:中间操作 vs 终端操作

JavaStream API 中,我们构建的是一个“流水线式的数据处理过程”,就像搭建一条工厂生产线:

  • 每一道工序是一个 中间操作(intermediate operation
  • 最后一道是一个 终端操作(terminal operation,它才启动整条流水线。

🧱 中间操作(Intermediate Operation):搭建流水线,不启动

定义:一个方法返回一个 Stream,就是一个中间操作。 作用:添加一个“处理环节”,但本身不触发数据处理。

Stream<String> stream = List.of("a", "bb", "ccc").stream();
Stream<Integer> lengths = stream.map(str -> str.length()); // 中间操作

✅ 你只是“配置”了处理流程,没有处理数据。就像装了一个“长度计算”的机械臂,还没开电源。


🔚 终端操作(Terminal Operation):触发流水线,处理数据

定义:一个方法返回的不是 Stream(或是 void),那就是终端操作。 作用:触发数据从源头一个个“通过中间操作”,并产出结果。

int total = List.of("a", "bb", "ccc")
    .stream()
    .map(String::length)      // 中间操作
    .reduce(0, Integer::sum); // 终端操作
System.out.println("总长度 = " + total); // 输出 6

✅ 数据此时才真正流过 map,然后被 reduce 聚合求和。


🔁 串联多个中间操作

var result = List.of("apple", "banana", "avocado")
    .stream()
    .filter(s -> s.startsWith("a"))   // 过滤出以 a 开头的
    .map(String::toUpperCase)         // 转大写
    .sorted()                         // 排序
    .toList();                        // 终端操作:生成结果列表

System.out.println(result); // 输出: [APPLE, AVOCADO]

📌 注意:这些 .filter(), .map(), .sorted() 每一步都只是排流程,只有 .toList() 才触发执行


⚠️ 常见误区:一个 Stream 只能使用一次!

var stream = Stream.of(1, 2, 3, 4);

// 第一次使用,合法
var mapped = stream.map(i -> i + 1);

// 第二次使用,❌ 非法!stream 已经用过
var list = stream.toList(); // 会抛 IllegalStateException

💥 错误信息:

java.lang.IllegalStateException: stream has already been operated upon or closed

🛠 正确方式:复用数据源,不复用 Stream 对象

// 正确做法:重新从集合创建新 Stream
var source = List.of(1, 2, 3, 4);

var mapped = source.stream().map(i -> i + 1).toList(); // ✅
var filtered = source.stream().filter(i -> i % 2 == 0).toList(); // ✅

🎯 中间 vs 终端操作对比总结

操作类型特征描述是否触发处理返回值类型示例方法
中间操作构建流水线,返回新的 Stream❌ 否Streammap, filter, sorted
终端操作启动流水线,返回结果或副作用✅ 是Stream 类型collect, toList, reduce, forEach

💡 比喻辅助记忆:

  • 中间操作:就像在工厂安装传送带上的“加工模块”,还没有开机。
  • 终端操作:按下启动按钮,产品才真正开始加工和流转。

📌 补充建议:可练习的小题目

  1. 使用 map()filter() 构建一个处理流程,但不要调用终端操作,验证是否执行了逻辑?
  2. 设计一个流程:过滤出偶数,再平方,然后求和。
  3. 尝试复用一个 Stream 对象,观察异常,并改用集合重新创建 Stream。