11.Java Stream API


✨博客主页: https://blog.csdn.net/m0_63815035?type=blog
💗《博客内容》:大数据、AI开发、Java、测试开发、Python、Android、Go、Node、Android前端小程序等相关领域知识
📢博客专栏: https://blog.csdn.net/m0_63815035/category_11954877.html
📢欢迎点赞 👍 收藏 ⭐留言 📝
📢本文为学习笔记资料,如有侵权,请联系我删除,疏漏之处还请指正🙉
📢大厦之成,非一木之材也;大海之阔,非一流之归也✨

目录
- 超详细版
-
- 一、Stream 概述
- 二、Stream 的创建
- 三、Stream 中间操作详解
- 四、Stream 终端操作详解
- 五、Optional 的使用
- 六、并行流注意事项
- 七、数值流与装箱优化
- 八、高级技巧与常见陷阱
- 九、完整示例
- 十、结尾
超详细版
Stream API 是 Java 8 引入的一套函数式数据处理工具,它允许你以声明式方式处理集合、数组等数据源。本文从核心概念到具体操作,结合大量代码示例,帮你彻底掌握 Stream 的使用。
一、Stream 概述
1.1 什么是 Stream?
Stream 不是数据结构,它不存储数据,而是对数据源(集合、数组、I/O 资源等)的一种视图,支持链式操作。你可以把它想象成一条流水线:数据从源头进入,经过一系列中间处理(过滤、转换、排序),最后通过终端操作产出结果。
Stream 的设计借鉴了函数式编程语言(如 SQL 查询数据的方式)。例如:
SELECT name FROM emp WHERE salary > 5000 ORDER BY salary DESC

用 Stream 实现:
empList.stream()
.filter(e -> e.getSalary() > 5000)
.sorted(Comparator.comparingDouble(Employee::getSalary).reversed())
.map(Employee::getName)
.forEach(System.out::println);
1.2 Stream 的核心特点
- 不存储数据:Stream 只是数据源的一个视图,不改变原有数据结构。
- 函数式编程风格:对数据的操作以 Lambda 表达式传递,代码简洁。
- 惰性求值:中间操作(如
filter、map)不会立即执行,只有遇到终端操作(如collect、forEach)才会触发实际计算。 - 可并行:通过
parallelStream()或parallel()将顺序流转为并行流,利用多核提升性能。 - 不可重复使用:一个 Stream 只能被消费一次,终端操作后流即关闭。
1.3 Stream 的操作分类
| 类型 | 方法示例 | 特点 |
|---|---|---|
| 中间操作(Intermediate) | filter, map, sorted, limit, distinct |
返回 Stream,可链式调用,惰性执行 |
| 终端操作(Terminal) | forEach, collect, reduce, count, min, max, anyMatch |
触发计算,返回非 Stream 结果(或 void) |
| 短路操作 | limit, skip, anyMatch, findFirst |
遇到足够结果即终止,可优化性能 |
二、Stream 的创建
2.1 从集合创建
集合接口(Collection)提供了 stream() 和 parallelStream() 方法。
List<String> list = Arrays.asList("a", "b", "c");
Stream<String> sequentialStream = list.stream(); // 顺序流
Stream<String> parallelStream = list.parallelStream(); // 并行流
// 顺序流转并行
Stream<String> parallel = list.stream().parallel();
2.2 从数组创建
使用 Arrays.stream() 或 Stream.of()。
int[] intArray = {1, 2, 3, 4, 5};
IntStream intStream = Arrays.stream(intArray); // 基本类型专用流
String[] strArray = {"a", "b", "c"};
Stream<String> strStream = Arrays.stream(strArray);
Stream<String> strStream2 = Stream.of("a", "b", "c");
2.3 使用 Stream.of() 直接创建
Stream<Integer> intStream = Stream.of(1, 2, 3, 4, 5);
Stream<String> strStream = Stream.of("Hello", "World");
2.4 使用 Stream.iterate() 和 Stream.generate()
这两个方法可以生成无限流,通常配合 limit() 使用。
// iterate:从种子开始,反复应用函数生成下一个元素
Stream.iterate(0, n -> n + 2)
.limit(10)
.forEach(System.out::println); // 0,2,4,6,8,10,12,14,16,18
// generate:无限调用 Supplier 产生元素
Stream.generate(Math::random)
.limit(5)
.forEach(System.out::println);
Java 9 增加了 iterate 的重载,可带条件终止:
Stream.iterate(0, n -> n < 100, n -> n + 2)
.forEach(System.out::println);
2.5 从文件行创建
try (Stream<String> lines = Files.lines(Paths.get("data.txt"), StandardCharsets.UTF_8)) {
lines.filter(line -> line.contains("error"))
.forEach(System.out::println);
} catch (IOException e) {
e.printStackTrace();
}
2.6 基本类型流(避免装箱)
Java 提供了 IntStream, LongStream, DoubleStream,用于处理基本类型,避免自动装箱带来的性能开销。
IntStream.range(1, 10).forEach(System.out::println); // [1,10)
IntStream.rangeClosed(1, 10).forEach(System.out::println); // [1,10]
2.7 顺序流 vs 并行流
- 顺序流:单线程处理,结果顺序稳定。
- 并行流:内部使用
ForkJoinPool,将任务拆分到多个线程执行。适用于数据量大、元素处理无依赖的场景。注意并行流可能带来线程安全问题。
// 并行流计算
long sum = LongStream.rangeClosed(1, 10_000_000)
.parallel()
.sum();
三、Stream 中间操作详解
3.1 遍历/匹配(forEach, peek)
forEach:终端操作,遍历每个元素。peek:中间操作,用于调试或观察元素流过,不影响结果。
list.stream()
.filter(x -> x > 5)
.peek(System.out::println) // 打印过滤后的元素
.collect(Collectors.toList());
3.2 筛选(filter)
接收 Predicate,过滤出符合条件的元素。
List<Employee> rich = employees.stream()
.filter(e -> e.getSalary() > 10000)
.collect(Collectors.toList());
3.3 映射(map, flatMap, mapToInt 等)
map:将每个元素转换成另一种形式。flatMap:将每个元素转换为一个 Stream,然后将多个 Stream 扁平连接成一个 Stream。
// map:提取员工姓名
List<String> names = employees.stream()
.map(Employee::getName)
.collect(Collectors.toList());
// flatMap:将多个字符串按分隔符拆分,合并成单词流
String[] lines = {"Hello world", "Java Stream"};
List<String> words = Arrays.stream(lines)
.flatMap(line -> Arrays.stream(line.split(" ")))
.collect(Collectors.toList()); // ["Hello", "world", "Java", "Stream"]
基本类型转换 mapToInt, mapToLong, mapToDouble:
IntStream salaryStream = employees.stream().mapToInt(Employee::getSalary);
3.4 排序(sorted)
- 无参:要求元素实现
Comparable。 - 带
Comparator:自定义排序。
// 自然排序(Employee 需实现 Comparable)
employees.stream().sorted().collect(Collectors.toList());
// 按薪资倒序
employees.stream()
.sorted(Comparator.comparingDouble(Employee::getSalary).reversed())
.collect(Collectors.toList());
// 多级排序:先按部门,再按薪资
employees.stream()
.sorted(Comparator.comparing(Employee::getDept)
.thenComparingDouble(Employee::getSalary))
.collect(Collectors.toList());
3.5 去重(distinct)
根据元素的 equals() 和 hashCode() 去除重复。
List<Integer> distinctNums = nums.stream().distinct().collect(Collectors.toList());
3.6 截断(limit, skip)
limit(n):保留前 n 个元素。skip(n):跳过前 n 个元素。
// 取前 5 个
Stream.iterate(1, i -> i + 1).limit(5).forEach(System.out::println); // 1,2,3,4,5
// 跳过前 2 个,再取 3 个
Stream.iterate(1, i -> i + 1).skip(2).limit(3).forEach(System.out::println); // 3,4,5
3.7 中间操作组合示例
List<String> result = employees.stream()
.filter(e -> e.getSalary() > 5000) // 过滤
.map(Employee::getName) // 映射成姓名
.distinct() // 去重
.sorted() // 排序
.limit(10) // 取前10
.collect(Collectors.toList());
四、Stream 终端操作详解
4.1 遍历(forEach, forEachOrdered)
// 顺序流,forEach 顺序不确定(并行流中顺序无保证)
list.parallelStream().forEach(System.out::println);
// 保证遇到顺序(并行时按源顺序处理,性能降低)
list.parallelStream().forEachOrdered(System.out::println);
4.2 匹配(anyMatch, allMatch, noneMatch)
返回 boolean,短路操作。
boolean anyAbove10000 = employees.stream().anyMatch(e -> e.getSalary() > 10000);
boolean allAbove0 = employees.stream().allMatch(e -> e.getSalary() > 0);
boolean noneBelow0 = employees.stream().noneMatch(e -> e.getSalary() < 0);
4.3 查找(findFirst, findAny)
返回 Optional,findAny 在并行流中性能更好。
Optional<Employee> first = employees.stream().findFirst();
Optional<Employee> any = employees.parallelStream().findAny();
4.4 聚合(count, min, max)
long count = employees.stream().count();
Optional<Employee> minSalary = employees.stream()
.min(Comparator.comparingDouble(Employee::getSalary));
Optional<Employee> maxSalary = employees.stream()
.max(Comparator.comparingDouble(Employee::getSalary));
4.5 归约(reduce)
将流元素反复组合,得到一个值。
// 求和
List<Integer> numbers = Arrays.asList(1, 2, 3, 4);
Optional<Integer> sum = numbers.stream().reduce((a, b) -> a + b); // 10
Integer sumWithIdentity = numbers.stream().reduce(0, Integer::sum); // 10
// 求最大值
Optional<Integer> max = numbers.stream().reduce(Integer::max);
// 字符串拼接
String concat = Stream.of("a", "b", "c").reduce("", (s1, s2) -> s1 + s2); // "abc"
reduce 的完整签名:
Optional<T> reduce(BinaryOperator<T> accumulator)T reduce(T identity, BinaryOperator<T> accumulator)<U> U reduce(U identity, BiFunction<U,? super T,U> accumulator, BinaryOperator<U> combiner)(用于并行流)
4.6 收集(collect)
collect 是最灵活的终端操作,可以将流转换为集合、Map、字符串或执行自定义归约。Collectors 工具类提供了大量工厂方法。
4.6.1 归集(toList, toSet, toMap)
List<String> nameList = employees.stream()
.map(Employee::getName)
.collect(Collectors.toList());
Set<Integer> deptSet = employees.stream()
.map(Employee::getDeptId)
.collect(Collectors.toSet());
Map<Integer, String> idToName = employees.stream()
.collect(Collectors.toMap(Employee::getId, Employee::getName));
// toMap 处理 key 冲突
Map<Integer, Employee> idToEmp = employees.stream()
.collect(Collectors.toMap(Employee::getId, Function.identity(), (oldVal, newVal) -> newVal));
4.6.2 统计(counting, summingInt, averagingDouble, summarizingInt)
long count = employees.stream().collect(Collectors.counting());
double avgSalary = employees.stream().collect(Collectors.averagingDouble(Employee::getSalary));
int totalSalary = employees.stream().collect(Collectors.summingInt(Employee::getSalary));
DoubleSummaryStatistics stats = employees.stream()
.collect(Collectors.summarizingDouble(Employee::getSalary));
System.out.println(stats.getCount() + ", " + stats.getSum() + ", " + stats.getAverage());
4.6.3 分组(groupingBy)
// 单级分组
Map<String, List<Employee>> byDept = employees.stream()
.collect(Collectors.groupingBy(Employee::getDept));
// 分组并计数
Map<String, Long> countByDept = employees.stream()
.collect(Collectors.groupingBy(Employee::getDept, Collectors.counting()));
// 多级分组:先按部门,再按薪资等级
Map<String, Map<String, List<Employee>>> multi = employees.stream()
.collect(Collectors.groupingBy(Employee::getDept,
Collectors.groupingBy(e -> e.getSalary() > 8000 ? "高薪" : "普通")));
4.6.4 分区(partitioningBy)
分区是分组的一种特例,按 true/false 分成两个组。
Map<Boolean, List<Employee>> partitioned = employees.stream()
.collect(Collectors.partitioningBy(e -> e.getSalary() > 10000));
4.6.5 连接(joining)
仅用于字符串流。
String names = employees.stream()
.map(Employee::getName)
.collect(Collectors.joining(", ", "[", "]"));
4.6.6 归约收集(reducing)
Collectors.reducing 提供了比 reduce 更灵活的归约方式,可与其他收集器组合。
// 所有员工薪资总和
Double total = employees.stream()
.collect(Collectors.reducing(0.0, Employee::getSalary, Double::sum));
4.7 终端操作组合示例
// 统计每个部门平均薪资,并筛选出平均薪资 > 8000 的部门
Map<String, Double> avgSalaryByDept = employees.stream()
.collect(Collectors.groupingBy(Employee::getDept,
Collectors.averagingDouble(Employee::getSalary)))
.entrySet().stream()
.filter(e -> e.getValue() > 8000)
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
五、Optional 的使用
Stream 的 findFirst, findAny, min, max, reduce 等方法返回 Optional,用于安全地处理可能为 null 的情况。
Optional<Employee> opt = employees.stream().findFirst();
if (opt.isPresent()) {
System.out.println(opt.get().getName());
}
opt.ifPresent(e -> System.out.println(e.getName())); // 更优雅
String name = opt.map(Employee::getName).orElse("Unknown");
六、并行流注意事项
- 线程安全:避免在并行流中修改外部共享状态。例如
forEach里不能直接list.add。 - 性能:数据量小或操作简单时,并行流可能比顺序流慢(线程调度开销)。
- 顺序依赖:需要保持顺序时使用
forEachOrdered或避免并行。 - 正确使用收集器:
Collectors.toList是线程安全的,但自定义收集器需注意。 - ForkJoinPool:默认使用公共池(
ForkJoinPool.commonPool()),可通过系统属性java.util.concurrent.ForkJoinPool.common.parallelism调整并行度;也可以使用自定义池:
ForkJoinPool pool = new ForkJoinPool(4);
pool.submit(() -> largeList.parallelStream().forEach(...)).join();
七、数值流与装箱优化
当处理大量基本类型时,使用专用流(IntStream, LongStream, DoubleStream)避免装箱/拆箱开销。
// 使用 IntStream.range 生成数字
int sum = IntStream.rangeClosed(1, 100).sum();
// 从对象流转数值流
IntStream ageStream = employees.stream().mapToInt(Employee::getAge);
八、高级技巧与常见陷阱
8.1 惰性求值示例
Stream<String> stream = list.stream()
.filter(s -> {
System.out.println("filter: " + s);
return s.length() > 3;
});
// 此时没有输出,因为尚未执行终端操作
stream.forEach(System.out::println);
// 输出 filter 和打印结果
8.2 无限流应限制大小
Stream.generate(Math::random).limit(10).forEach(...);
8.3 避免在 forEach 中修改外部集合
// 错误示例
List<Integer> result = new ArrayList<>();
stream.forEach(result::add); // 非线程安全,尤其在并行流中
// 正确做法
List<Integer> result = stream.collect(Collectors.toList());
8.4 重用 Stream 的误区
Stream<String> s = list.stream();
s.forEach(System.out::println);
s.forEach(System.out::println); // 异常:stream has already been operated upon or closed
8.5 flatMap 与 map 的区别
map:一对一转换,输出流元素个数等于输入流。flatMap:一对多转换,每个输入元素可能产生 0 个、1 个或多个输出元素,然后扁平化。
// flatMap 示例:将每个句子拆成单词
Stream<String> lines = Stream.of("Hello world", "Java stream");
Stream<String> words = lines.flatMap(line -> Arrays.stream(line.split(" ")));
九、完整示例
import java.util.*;
import java.util.stream.*;
public class StreamDemo {
static class Employee {
int id;
String name;
String dept;
double salary;
// 构造器、getter、setter、toString 省略
}
public static void main(String[] args) {
List<Employee> employees = generateEmployees();
// 找出部门 "IT" 中薪资最高的前 3 名员工的姓名
List<String> top3IT = employees.stream()
.filter(e -> "IT".equals(e.getDept()))
.sorted(Comparator.comparingDouble(Employee::getSalary).reversed())
.limit(3)
.map(Employee::getName)
.collect(Collectors.toList());
// 统计各部门平均薪资
Map<String, Double> avgSalaryByDept = employees.stream()
.collect(Collectors.groupingBy(Employee::getDept,
Collectors.averagingDouble(Employee::getSalary)));
// 总薪资
double totalSalary = employees.stream()
.mapToDouble(Employee::getSalary)
.sum();
}
}
十、结尾
Stream API 是 Java 函数式编程的核心,熟练掌握它可以让数据处理的代码更简洁、可读性更强,并利用并行计算提升性能。建议:
- 多使用
collect替代显式循环。 - 优先使用方法引用(如
Employee::getSalary)保持代码简洁。 - 合理使用并行流(数据量大、无状态、不依赖顺序时)。
- 注意惰性求值的特点,避免忘记终端操作导致流不执行。
- 利用
Optional安全处理可能为空的结果。
通过大量练习,你会发现 Stream 能解决日常开发中 80% 以上的集合处理问题。
今天这篇文章就到这里了,大厦之成,非一木之材也;大海之阔,非一流之归也。感谢大家观看本文

更多推荐



所有评论(0)