Java 8 Stream 流实战指南:从入门到进阶
【摘要】 Java 8 引入的 Stream API 是处理集合数据的利器,用好了能让代码简洁优雅,用不好反而让人看不懂。本文带你从零开始掌握 Stream 流的核心用法,涵盖常用操作、实战案例和避坑指南,适合日常开发查阅。 一、什么是 Stream?Stream 是对集合(Collection)的增强,它允许你以声明式的方式处理数据。简单说,以前遍历集合要写 for 循环,现在可以用链式调用一步到位...
Java 8 引入的 Stream API 是处理集合数据的利器,用好了能让代码简洁优雅,用不好反而让人看不懂。本文带你从零开始掌握 Stream 流的核心用法,涵盖常用操作、实战案例和避坑指南,适合日常开发查阅。
一、什么是 Stream?
Stream 是对集合(Collection)的增强,它允许你以声明式的方式处理数据。简单说,以前遍历集合要写 for 循环,现在可以用链式调用一步到位。
Stream 有三个特点:
- 不存储数据:Stream 只是数据的"管道",数据来自集合、数组或 IO 源
- 不修改源数据:Stream 操作不会改变原始集合,而是返回新结果
- 惰性求值:中间操作不会立即执行,只有遇到终端操作才会真正计算
二、Stream 的操作分类
Stream 操作分为两类:
| 类型 | 说明 | 常见方法 |
|---|---|---|
| 中间操作 | 返回 Stream,可链式调用 | filter、map、sorted、distinct、limit、skip |
| 终端操作 | 返回具体结果,触发执行 | collect、forEach、count、reduce、anyMatch、findFirst |
一个 Stream 只能被消费一次,终端操作执行后 Stream 就关闭了,再次使用会抛异常。
三、常用操作详解
1. 创建 Stream
import java.util.*;
import java.util.stream.*;
public class StreamCreateDemo {
public static void main(String[] args) {
// 从集合创建
List<String> list = Arrays.asList("Java", "Python", "Go");
Stream<String> stream1 = list.stream();
// 从数组创建
Stream<Integer> stream2 = Stream.of(1, 2, 3, 4, 5);
// 从范围创建
IntStream stream3 = IntStream.rangeClosed(1, 10);
// 无限流
Stream<Double> stream4 = Stream.generate(Math::random).limit(5);
}
}
2. filter:过滤
List<Integer> nums = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
List<Integer> evens = nums.stream()
.filter(n -> n % 2 == 0)
.collect(Collectors.toList());
// [2, 4, 6, 8, 10]
3. map:转换
List<String> names = Arrays.asList("java", "python", "go");
List<String> upper = names.stream()
.map(String::toUpperCase)
.collect(Collectors.toList());
// ["JAVA", "PYTHON", "GO"]
4. flatMap:扁平化
List<List<Integer>> nested = Arrays.asList(
Arrays.asList(1, 2),
Arrays.asList(3, 4),
Arrays.asList(5, 6)
);
List<Integer> flat = nested.stream()
.flatMap(List::stream)
.collect(Collectors.toList());
// [1, 2, 3, 4, 5, 6]
5. sorted:排序
List<Integer> nums = Arrays.asList(5, 3, 8, 1, 9);
List<Integer> sorted = nums.stream()
.sorted()
.collect(Collectors.toList());
// [1, 3, 5, 8, 9]
// 自定义排序
List<String> names = Arrays.asList("Java", "python", "Go", "Rust");
List<String> sorted2 = names.stream()
.sorted(String.CASE_INSENSITIVE_ORDER)
.collect(Collectors.toList());
// ["Go", "Java", "python", "Rust"]
6. distinct:去重
List<Integer> nums = Arrays.asList(1, 2, 2, 3, 3, 3, 4);
List<Integer> unique = nums.stream()
.distinct()
.collect(Collectors.toList());
// [1, 2, 3, 4]
7. limit / skip:截取
List<Integer> nums = IntStream.rangeClosed(1, 10).boxed().collect(Collectors.toList());
List<Integer> result = nums.stream()
.skip(3) // 跳过前3个
.limit(4) // 取4个
.collect(Collectors.toList());
// [4, 5, 6, 7]
四、终端操作
1. collect:收集结果
List<String> names = Arrays.asList("Java", "Python", "Go", "Rust");
// 转 List
List<String> list = names.stream().collect(Collectors.toList());
// 转 Set
Set<String> set = names.stream().collect(Collectors.toSet());
// 转 Map
Map<String, Integer> map = names.stream()
.collect(Collectors.toMap(
name -> name,
String::length
));
// {Java=4, Python=6, Go=2, Rust=4}
// 分组
Map<Integer, List<String>> grouped = names.stream()
.collect(Collectors.groupingBy(String::length));
// {2=[Go], 4=[Java, Rust], 6=[Python]}
// 拼接字符串
String joined = names.stream()
.collect(Collectors.joining(", "));
// "Java, Python, Go, Rust"
2. forEach:遍历
List<String> names = Arrays.asList("Java", "Python", "Go");
names.stream().forEach(System.out::println);
3. reduce:归约
List<Integer> nums = Arrays.asList(1, 2, 3, 4, 5);
// 求和
int sum = nums.stream().reduce(0, Integer::sum);
// 15
// 求最大值
Optional<Integer> max = nums.stream().reduce(Integer::max);
// Optional[5]
// 拼接字符串
String result = nums.stream()
.map(String::valueOf)
.reduce("", (a, b) -> a + b);
// "12345"
4. 匹配与查找
List<Integer> nums = Arrays.asList(1, 2, 3, 4, 5);
boolean anyEven = nums.stream().anyMatch(n -> n % 2 == 0); // true
boolean allPositive = nums.stream().allMatch(n -> n > 0); // true
boolean noneNegative = nums.stream().noneMatch(n -> n < 0); // true
Optional<Integer> first = nums.stream().filter(n -> n > 3).findFirst(); // Optional[4]
Optional<Integer> any = nums.stream().filter(n -> n > 3).findAny(); // Optional[4]
五、实战案例
案例 1:从用户列表中提取符合条件的数据
@Data
@AllArgsConstructor
class User {
private String name;
private Integer age;
private String city;
private BigDecimal salary;
}
List<User> users = Arrays.asList(
new User("张三", 25, "北京", new BigDecimal("15000")),
new User("李四", 30, "上海", new BigDecimal("25000")),
new User("王五", 28, "北京", new BigDecimal("18000")),
new User("赵六", 35, "杭州", new BigDecimal("30000")),
new User("钱七", 22, "上海", new BigDecimal("12000"))
);
// 找出北京年龄大于25的用户,按薪资降序,取前2个的名字
List<String> result = users.stream()
.filter(u -> "北京".equals(u.getCity()))
.filter(u -> u.getAge() > 25)
.sorted(Comparator.comparing(User::getSalary).reversed())
.limit(2)
.map(User::getName)
.collect(Collectors.toList());
// ["王五"]
案例 2:分组统计
// 按城市分组,统计平均薪资
Map<String, Double> avgSalaryByCity = users.stream()
.collect(Collectors.groupingBy(
User::getCity,
Collectors.averagingDouble(u -> u.getSalary().doubleValue())
));
// {北京=16500.0, 上海=18500.0, 杭州=30000.0}
// 按城市分组,取每组薪资最高的用户
Map<String, Optional<User>> topByCity = users.stream()
.collect(Collectors.groupingBy(
User::getCity,
Collectors.maxBy(Comparator.comparing(User::getSalary))
));
案例 3:多级分组
// 先按城市分组,再按年龄是否大于25分组
Map<String, Map<Boolean, List<User>>> grouped = users.stream()
.collect(Collectors.groupingBy(
User::getCity,
Collectors.partitioningBy(u -> u.getAge() > 25)
));
六、避坑指南
1. Stream 只能消费一次
Stream<String> stream = list.stream();
stream.forEach(System.out::println);
stream.forEach(System.out::println); // ❌ IllegalStateException
2. 不要在 forEach 中修改外部变量
int sum = 0;
nums.stream().forEach(n -> sum += n); // ❌ 编译错误,sum 必须是 final
应该用 reduce 或 summingInt。
3. parallelStream 不是万能的
list.parallelStream().forEach(System.out::println); // 输出顺序不确定
并行流适合 CPU 密集型的大数据量计算,小数据量反而更慢。而且要注意线程安全问题,不要在并行流中操作共享可变状态。
4. collect(toMap) 键重复会抛异常
// 如果有重复 key,会抛 IllegalStateException
Map<String, Integer> map = users.stream()
.collect(Collectors.toMap(User::getCity, User::getAge));
// 解决:提供合并函数
Map<String, Integer> map2 = users.stream()
.collect(Collectors.toMap(
User::getCity,
User::getAge,
(v1, v2) -> v1 // 保留第一个
));
七、常用 Collectors 速查
| 方法 | 用途 |
|---|---|
toList() |
转 List |
toSet() |
转 Set |
toMap(key, value) |
转 Map |
joining(delimiter) |
拼接字符串 |
counting() |
计数 |
summingInt/Long/Double |
求和 |
averagingInt/Long/Double |
求平均 |
maxBy/minBy(comparator) |
最大/最小值 |
groupingBy(classifier) |
分组 |
partitioningBy(predicate) |
分区(true/false 两组) |
reducing(identity, op) |
自定义归约 |
总结
Stream 流让集合操作变得简洁优雅,但也要避免过度使用。简单的遍历用 for 循环反而更清晰,复杂的链式调用要注意可读性,必要时可以拆成多行或提取方法。掌握 Stream 的核心操作和 Collectors,日常开发中 80% 的集合处理场景都能优雅搞定。
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱:
cloudbbs@huaweicloud.com
- 点赞
- 收藏
- 关注作者
评论(0)