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。
相关推荐
路修远i2 小时前
前端单元测试
前端·单元测试
悟空码字2 小时前
Java短信验证码保卫战,当羊毛党遇上“铁公鸡”
java·后端
爱吃KFC的大肥羊2 小时前
Redis 基础完全指南:从全局命令到五大数据结构
java·开发语言·数据库·c++·redis·后端
用户2190326527352 小时前
Spring Boot4.0整合RabbitMQ死信队列详解
java·后端
golang学习记2 小时前
Go Gin 全局异常处理:别让 panic 把你的服务“原地升天”
后端
Targo2 小时前
Go 高可用策略库-Resilience
后端·go
不一样的少年_2 小时前
【用户行为监控】别只做工具人了!手把手带你写一个前端埋点统计 SDK
前端·javascript·监控
天天摸鱼的java工程师2 小时前
🚪单点登录实战:同端同账号互踢下线的最佳实践(Java 实现)
java·后端
小飞Coding2 小时前
Java堆外内存里的“密文”--从内存内容反推业务模块实战
jvm·后端