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。
相关推荐
Dragon Wu38 分钟前
Spring Security Oauth2.1 授权码模式实现前后端分离的方案
java·spring boot·后端·spring cloud·springboot·springcloud
一个有梦有戏的人1 小时前
Python3基础:进阶基础,筑牢编程底层能力
后端·python
夏幻灵1 小时前
HTML5里最常用的十大标签
前端·html·html5
爬山算法1 小时前
Hibernate(88)如何在负载测试中使用Hibernate?
java·后端·hibernate
Mr Xu_1 小时前
Vue 3 中 watch 的使用详解:监听响应式数据变化的利器
前端·javascript·vue.js
未来龙皇小蓝1 小时前
RBAC前端架构-01:项目初始化
前端·架构
程序员agions2 小时前
2026年,微前端终于“死“了
前端·状态模式
万岳科技系统开发2 小时前
食堂采购系统源码库存扣减算法与并发控制实现详解
java·前端·数据库·算法
独断万古他化2 小时前
【Spring 原理】Bean 的作用域与生命周期
java·后端·spring
程序员猫哥_2 小时前
HTML 生成网页工具推荐:从手写代码到 AI 自动生成网页的进化路径
前端·人工智能·html