270. Java Stream API - 从“怎么做”转向“要什么结果”:声明式编程的优势

270. Java Stream API - 从"怎么做"转向"要什么结果":声明式编程的优势

🎯 目标任务

我们要计算一个城市列表中,人口超过 10 万的城市总人口数


🧱 传统命令式写法(Java 代码)

java 复制代码
record City(int population) {}

List<City> cities = List.of(
    new City(100_000),
    new City(200_000),
    new City(500_000)
);

int sum = 0;
for (City city : cities) {
    int population = city.population();
    if (population > 100_000) {
        sum += population;
    }
}
System.out.println("Sum = " + sum); // 输出:700000

这段代码很好地完成了目标,但是命令式的------一步一步告诉程序要做什么。


💡 思维实验:如果 Collection 有 map 和 filter 会怎样?

假设我们扩展 Collection 接口,给它添加 map()filter() 方法,并且它们返回的是 Collection

java 复制代码
Collection<Integer> populations = cities.map(city -> city.population());
Collection<Integer> filteredPopulations = populations.filter(p -> p > 100_000);
int sum = filteredPopulations.sum();

看起来很"链式",很优雅,但有一个严重问题每一步都要创建中间集合!


❌ 性能陷阱:中间集合的隐性开销

  • map() 会遍历所有城市,并创建一个新的集合保存每个城市的人口数。
  • filter() 会再遍历这个人口集合,选出符合条件的。
  • sum() 会再遍历过滤结果做加总。

👉 如果处理 上百万城市对象 ,那么这会造成大量 内存分配和垃圾回收压力

而传统的 for 循环是一边遍历、一边判断、一边累加的,没有任何中间结构的创建


✅ 为什么是 Stream,而不是 Collection?

🌊 Stream 的关键特性:不存储数据,只描述处理过程

java 复制代码
int sum = cities.stream()
    .mapToInt(City::getPopulation)       // 先映射成人口数
    .filter(p -> p > 100_000)         // 筛选出人口超 10 万的
    .sum();                           // 聚合求和(终端操作)

在这段代码中:

  • 没有中间集合产生;
  • .mapToInt().filter() 都是中间操作,只是"排管道";
  • .sum()终端操作,才真正触发数据流动和计算。

🧠 懒加载(Lazy Evaluation):终端操作才"开水龙头"

  • Stream 就像"工厂流水线",每个操作(map/filter)都是一个加工环节;
  • 只有当你调用 .sum().collect() 等终端操作时,才真的开始处理每个数据;
  • 每个数据只"走一次管道":

💡 不是"先映射完所有人口" ➝ "再筛选" ➝ "再加总", 而是:每个城市 ➝ 映射 ➝ 判断是否保留 ➝ 累加(即一条龙服务🚀)。


⚙️ 示例:短路操作节省时间

需求:判断是否存在人口超过 100_000 的城市

传统 Collection 风格必须遍历:

java 复制代码
boolean exists = cities.map(City::population)
                       .filter(p -> p > 100_000)
                       .anyMatch(p -> true);  // 已经浪费了两步处理

而用 Stream 可以做到只看第一个符合条件的城市

java 复制代码
boolean exists = cities.stream()
                       .anyMatch(c -> c.getPopulation() > 100_000);

💥 一旦遇到满足条件的城市,就立即返回 true,后续不再处理!


📊 总结:为什么 map/filter 不属于 Collection 接口?

方式 map/filter 返回类型 是否创建中间集合 是否惰性求值 是否支持短路
Collection.map() Collection ✅ 会创建 ❌ 否 ❌ 否
Stream.map() Stream ❌ 不创建 ✅ 是 ✅ 是(如 anyMatch)

👉 正因为Collection 是数据容器 ,而 Stream 是操作流水线 ,所以 map()filter() 被设计在 Stream 而非 Collection


🧵 Stream API 的流水线模型:终端操作才"触发机器运转"

操作类型 举例 描述
中间操作 map(), filter(), sorted() 返回 Stream,不触发计算
终端操作 sum(), collect(), forEach() 返回非 Stream,触发计算

📘 实操建议(练习题):

  1. 使用 stream() 实现:计算人口超过 200_000 城市的平均人口;
  2. 统计有多少个城市符合人口 > 100_000;
  3. 判断是否所有城市人口都 > 50_000。
相关推荐
踩着两条虫2 分钟前
AI 驱动的 Vue3 应用开发平台 深入探究(十九):CLI与工具链之Create VTJ CLI 参考
前端·ai编程·vite
天下无贼!13 分钟前
【Python】2026版——FastAPI 框架快速搭建后端服务
开发语言·前端·后端·python·aigc·fastapi
大傻^15 分钟前
Spring AI Alibaba Agent开发:基于ChatClient的智能体构建模式
java·数据库·人工智能·后端·spring·springaialibaba
大傻^36 分钟前
Spring AI Alibaba ChatClient实战:流式输出与多轮对话管理
java·人工智能·后端·spring·springai·springaialibaba
GISer_Jing1 小时前
两种AI交互方式深度解析——浏览器书签&插件
前端·人工智能·ai·prompt
SuniaWang1 小时前
《Spring AI + 大模型全栈实战》学习手册系列· 专题二:《Milvus 向量数据库:从零开始搭建 RAG 系统的核心组件》
java·人工智能·分布式·后端·spring·架构·typescript
前端布鲁伊1 小时前
零代码上线一个图片处理网站,我是如何使唤AI干活的?
前端·ai编程
庄小焱1 小时前
React——React基础语法(2)
前端·javascript·react.js
张小洛1 小时前
Spring 常用类深度剖析(工具篇 02):ReflectionUtils——优雅操作反射的利器
java·后端·spring·工具类·spring常用类