曾经需要30行代码的集合处理,现在用Stream流3行就能搞定!
Java 8引入的Stream API是Java函数式编程的重要里程碑,它彻底改变了我们处理集合数据的方式。无论是数据筛选、转换、聚合还是分组,Stream都能用声明式的语法简化代码逻辑。本文将带你全面掌握Java Stream流的使用技巧。
一、为什么需要Stream流?
在传统Java编程中,集合操作通常需要大量的循环和条件判断,代码冗长且容易出错。让我们先看一个对比示例:
scss
// 传统写法:筛选及格学生并排序(12行)
List<Student> passed = new ArrayList<>();
for (Student s : students) {
if (s.getScore() >= 60) {
passed.add(s);
}
}
Collections.sort(passed, (s1, s2) -> s2.getScore() - s1.getScore());
// Stream流写法:同样功能(3行!)
List<Student> passed = students.stream()
.filter(s -> s.getScore() >= 60)
.sorted(Comparator.comparingInt(Student::getScore).reversed())
.collect(Collectors.toList());
Stream流带来了三大优势:代码简洁性 (减少50%-70%代码量)、可读性提升 (声明式编程)和性能优化(并行处理能力)。
二、Stream流核心概念
2.1 什么是Stream?
Stream不是IO流,而是对集合进行函数式操作的流水线。可以将其想象为工厂的装配线: 集合 -> [过滤] -> [转换] -> [排序] -> [收集结果] Stream的主要特点包括:
- 不修改源数据:所有操作生成新流,不影响原始集合
- 惰性求值:中间操作延迟执行,只有终结操作才会触发实际计算
- 可并行处理 :通过
.parallel()一键启用多线程加速
2.2 Stream操作的三个阶段
| 阶段 | 方法举例 | 说明 |
|---|---|---|
| 创建 | .stream(), .parallelStream() |
从集合创建流 |
| 中间操作 | filter(), map(), sorted() |
链式调用,返回新Stream |
| 终结操作 | collect(), forEach(), count() |
触发计算并关闭流 |
三、创建Stream流
Stream可以从多种数据源创建:
ini
// 1. 从集合创建
List<String> list = Arrays.asList("Apple", "Banana", "Cherry");
Stream<String> stream1 = list.stream();
// 2. 从数组创建
String[] array = {"Java", "Python", "JavaScript"};
Stream<String> stream2 = Arrays.stream(array);
// 3. 使用Stream.of()创建
Stream<String> stream3 = Stream.of("A", "B", "C");
// 4. 创建并行流
Stream<String> parallelStream = list.parallelStream();
// 5. 从文件创建
Stream<String> lines = Files.lines(Paths.get("file.txt"));
四、常用Stream操作详解
4.1 中间操作(Intermediate Operations)
中间操作返回新的Stream,支持链式调用: filter() - 过滤元素
scss
// 筛选年龄大于18的学生
List<Student> adults = students.stream()
.filter(s -> s.getAge() > 18)
.collect(Collectors.toList());
map() - 元素转换
scss
// 提取学生姓名列表
List<String> names = students.stream()
.map(Student::getName) // 方法引用,等价于 s -> s.getName()
.collect(Collectors.toList());
// 转换为其他类型
List<Integer> nameLengths = students.stream()
.map(Student::getName)
.map(String::length)
.collect(Collectors.toList());
distinct() - 去重
ini
List<Integer> numbers = Arrays.asList(1, 2, 2, 3, 3, 3);
List<Integer> distinctNumbers = numbers.stream()
.distinct()
.collect(Collectors.toList()); // [1, 2, 3]
sorted() - 排序
less
// 按年龄升序排序
List<Student> sortedByAge = students.stream()
.sorted(Comparator.comparingInt(Student::getAge))
.collect(Collectors.toList());
// 按分数降序排序
List<Student> sortedByScoreDesc = students.stream()
.sorted(Comparator.comparingInt(Student::getScore).reversed())
.collect(Collectors.toList());
limit() / skip() - 分页操作
scss
// 获取第3-5名学生
List<Student> page = students.stream()
.skip(2) // 跳过前2个
.limit(3) // 取3个
.collect(Collectors.toList());
4.2 终结操作(Terminal Operations)
终结操作触发计算并返回结果,同时关闭流。 collect() - 结果收集 这是最强大的终结操作,可以将流转换为各种集合:
scss
// 转换为List
List<String> nameList = students.stream()
.map(Student::getName)
.collect(Collectors.toList());
// 转换为Set(自动去重)
Set<String> nameSet = students.stream()
.map(Student::getName)
.collect(Collectors.toSet());
// 转换为Map
Map<String, Integer> nameAgeMap = students.stream()
.collect(Collectors.toMap(
Student::getName,
Student::getAge
));
// 分组操作
Map<String, List<Student>> groupByMajor = students.stream()
.collect(Collectors.groupingBy(Student::getMajor));
// 分区操作(按条件分为true/false两组)
Map<Boolean, List<Student>> passedFailed = students.stream()
.collect(Collectors.partitioningBy(s -> s.getScore() >= 60));
forEach() - 遍历元素
less
students.stream()
.filter(s -> s.getScore() >= 90)
.forEach(s -> System.out.println(s.getName() + ": " + s.getScore()));
匹配检查
ini
boolean allPassed = students.stream()
.allMatch(s -> s.getScore() >= 60); // 是否全部及格
boolean anyPerfect = students.stream()
.anyMatch(s -> s.getScore() == 100); // 是否有满分
boolean noneFailed = students.stream()
.noneMatch(s -> s.getScore() < 60); // 是否没有挂科
统计操作
scss
// 简单的计数
long count = students.stream().count();
// 数值流统计
IntSummaryStatistics stats = students.stream()
.mapToInt(Student::getScore)
.summaryStatistics();
System.out.println("平均分: " + stats.getAverage());
System.out.println("最高分: " + stats.getMax());
System.out.println("总分: " + stats.getSum());
reduce() - 归约操作
scss
// 计算总分
int totalScore = students.stream()
.map(Student::getScore)
.reduce(0, Integer::sum);
// 查找最高分学生
Optional<Student> topStudent = students.stream()
.reduce((s1, s2) -> s1.getScore() > s2.getScore() ? s1 : s2);
五、实战应用场景
5.1 数据处理管道
在实际开发中,我们经常需要组合多个操作:
less
// 复杂数据处理示例
Map<String, Double> result = students.stream()
.filter(s -> s.getAge() >= 18) // 筛选成年学生
.filter(s -> s.getScore() >= 60) // 筛选及格学生
.sorted(Comparator.comparing(Student::getScore).reversed()) // 按分数降序
.limit(10) // 取前10名
.collect(Collectors.groupingBy(
Student::getMajor, // 按专业分组
Collectors.averagingDouble(Student::getScore) // 计算平均分
));
5.2 扁平化处理(flatMap)
当需要处理嵌套集合时,flatMap非常有用:
scss
// 多个班级的学生合并处理
List<Student> allStudents = classes.stream()
.flatMap(clazz -> clazz.getStudents().stream()) // 将多个List扁平化
.distinct() // 去重
.collect(Collectors.toList());
// 拆分字符串并扁平化
List<String> words = lines.stream()
.flatMap(line -> Arrays.stream(line.split(" "))) // 按空格拆分
.distinct()
.collect(Collectors.toList());
5.3 并行流处理大数据集
对于大数据集,使用并行流可以充分利用多核CPU:
scss
// 并行计算总分
int total = students.parallelStream()
.mapToInt(Student::getScore)
.sum();
// 并行流注意事项:确保操作是线程安全的
Map<String, List<Student>> parallelGroup = students.parallelStream()
.collect(Collectors.groupingByConcurrent(Student::getMajor));
六、性能考量与最佳实践
6.1 何时使用Stream?
- ✅ 适合场景:复杂的数据处理管道、大数据集操作、需要并行处理的场景
- ❌ 不适合场景:简单的遍历(直接使用for循环)、小数据集(Stream创建有开销)
6.2 性能优化技巧
- 使用基本类型流避免装箱开销:
scss
// 使用IntStream替代Stream<Integer>
int totalAge = students.stream()
.mapToInt(Student::getAge) // 返回IntStream
.sum();
- 短路操作优化性能:
scss
// 找到第一个及格的学生就停止
Optional<Student> firstPassed = students.stream()
.filter(s -> s.getScore() >= 60)
.findFirst();
- 避免重复创建流:
scss
// 错误做法:在循环内重复创建流
for (String name : names) {
long count = students.stream() // 每次循环都创建新流
.filter(s -> s.getName().equals(name))
.count();
}
// 正确做法:预先创建流
Stream<Student> studentStream = students.stream();
for (String name : names) {
long count = studentStream // 重用流(注意:流只能使用一次)
.filter(s -> s.getName().equals(name))
.count();
}
七、常见问题与解决方案
7.1 空指针处理
scss
// 安全处理可能为null的字段
List<String> safeNames = students.stream()
.map(Student::getName)
.filter(Objects::nonNull) // 过滤null值
.collect(Collectors.toList());
7.2 保持顺序的去重
ini
// 使用LinkedHashSet保持顺序
LinkedHashSet<Student> orderedSet = students.stream()
.collect(Collectors.toCollection(LinkedHashSet::new));
八、总结
Java Stream流通过声明式的函数式编程风格,大幅提升了集合处理的简洁性和可读性。关键要点总结:
- 创建流是起点,可从集合、数组等多种数据源创建
- 中间操作构建处理管道,支持过滤、转换、排序等操作
- 终结操作触发计算并产生结果,collect()是最强大的收集器
- 并行流适合大数据处理,但要注意线程安全问题
- 方法引用 (如
Student::getName)让代码更简洁