1. 命令式方法的问题
先从一个经典问题开始。假设我们有一个用户列表:
List<User> users = ...; // 已填充
假设我们需要获取所有已成年的用户(18 岁及以上)的姓名列表。过去我们会怎么做?
List<String> names = new ArrayList<>();
for (User user : users) {
if (user.getAge() >= 18) {
names.add(user.getName());
}
}
乍看之下很简单,但请注意这些“技术性”步骤:声明一个结果列表、遍历所有元素、检查条件、把结果加入列表。
如果需求稍微复杂一点——例如,要获取按字母顺序排序的、所有用户名以字母“A”开头的用户的唯一 e‑mail 列表——代码会很快膨胀:过滤、提取字段、去重、排序,甚至还可能要规范化 e‑mail 格式,等等。结果原本简短明晰的逻辑变成一大堆样板代码,既难读又难维护。
这种做法的缺点可以概括为三点:大量重复代码、容易出错(比如忘了排序或错误地处理了重复项),以及难以组合操作。这就是所谓的 命令式风格:你一步步告诉计算机,如何执行任务,而不是你想要得到什么。
2. Stream API —— 声明式风格
随着 Java 8 的发布,出现了 Stream API——一个让你以“做什么”而非“怎么做”的方式来处理集合的工具。这种方式称为 声明式。
什么是 Stream?
它不是一个独立的集合,而更像是数据流:元素序列依次通过一串操作——过滤、转换、排序、收集结果等。流本身不存储数据,只是把数据“输送”过处理管线。各类操作几乎可以像乐高积木一样自由组合:把链条拼好然后运行即可。
示例:
List<String> names = users.stream() // 从用户列表创建流
.filter(u -> u.getAge() >= 18) // 仅保留年龄≥18 的用户
.map(User::getName) // 将 User → String(取姓名)
.collect(Collectors.toList()); // 把结果收集为列表
就是这样——用一条链式表达就把任务描述清楚了:“取用户、按年龄过滤、取姓名、收集为列表”。无需手写循环、创建临时变量,也不用担心遗漏步骤。
Stream API 像工厂流水线一样工作:你定义处理的各个阶段,数据会自己流经这些阶段。简洁、美观,而且可读性大幅提升。
3. Stream API 的优势
- 简洁与可读性。 把同一任务用命令式和声明式各实现一遍,你会立刻看出哪种更易读、更易维护。
- 易于组合操作。 操作可以轻松“拼接”成链:filter、map、sorted、collect——全部都在同一条连贯的流水线上完成。
- 更少出错。 Stream API 帮你摆脱样板活:无需手动管理中间集合和循环——减少漏步骤或下标错误的机会。
- 支持并行处理。 只需把 parallelStream() 替换 stream(),就能轻松把处理扩展到多核。细节——稍后介绍。
- 现代编程风格。 函数式编程的理念(函数组合、无显式循环)提升表达力,也更受市场青睐。
Stream API 的简史
Stream API 于 Java 8(2014 年)引入,成为该语言的一次质的飞跃。在此之前,稍微复杂些的集合操作都要写大量辅助代码,而其他平台早已提供了声明式方式(map、filter、reduce 等)。
自那以后,Stream API 已成为 Java 进行集合处理的事实标准。想写出现代化的 Java 代码——离不开它。
4. Stream API 的适用场景
- 过滤: 只保留需要的元素(例如,所有大于 18 岁的用户)。
- 转换: 提取字段或创建新对象(例如,用户姓名列表)。
- 聚合: 计算总和、平均值、数量、最大/最小值。
- 排序: 按需要的键对元素排序。
- 分组: 按类别对元素划分。
- 收集结果: 收集为列表、集合、映射、字符串等。
示例任务:
- 获取所有姓名以字母“A”开头的用户的 e‑mail 列表。
- 计算用户的平均年龄。
- 查找第一个具有特定 e‑mail 的用户。
- 把所有不重复的居住城市收集到一个集合中。
5. 实用细节
流是如何工作的
[User1] --\
[User2] ---|--> [ filter ] --> [ map ] --> [ collect ] --> List<String>
[User3] --/
1. filter——仅保留满足条件的用户。
2. map——把用户转换为某个字段,例如 e‑mail。
3. collect——把 e‑mail 收集到所需的集合中。
语法:如何创建流
- 来自集合:list.stream() —— 集合元素的流。
- 来自数组:Arrays.stream(array)
- 来自若干值:Stream.of("a", "b", "c")
6. 迁移到 Stream API 时的常见错误
错误 1:在流内部修改集合。 Stream API 并不用于修改源集合(例如,不要在 forEach 的函数体里调用 list.remove())。需要删除元素时,可在处理前后直接对集合使用 removeIf 等方法。
错误 2:混淆集合与流。 流不是集合!流只会“走过”元素一次,随后即关闭。若需再次处理数据——请创建一个新的 stream()。
错误 3:链式调用过于复杂。 不要把表达式变成由十几步组成的“怪物”。如果链条变得很长——请拆成若干步骤,并使用中间变量以提升可读性和调试体验。
GO TO FULL VERSION