Java 8、Stream、函数式编程、Lambda、集合处理
一、什么是 Stream 流
Stream 是 Java 8 引入的关键抽象,它不是数据结构,不存储数据,而是用于对集合(Collection)、数组等数据源进行函数式、声明式的批量数据处理。
简单理解:Stream 就像一条"流水线",数据从源头流入,经过一道道工序(中间操作),最终产出结果(终端操作)。
核心思想
- 流水线思想:数据从源头经过一系列中间操作得到结果,每个操作不修改原始数据源,而是产生新的 Stream。
- 函数式编程:使用 Lambda 表达式和方法引用,代码更简洁、可读性更强。
- 惰性求值:中间操作(filter、map 等)不会立即执行,只有遇到终端操作(collect、forEach)时才触发计算。
- 内部迭代:由 Stream API 内部完成迭代,开发者只需关注"做什么",而非"怎么做"。
二、Stream 的核心特点
| 特点 | 说明 |
|---|---|
| 不存储数据 | Stream 本身不保存元素,只是对数据源的视图/管道 |
| 函数式编程 | 对 Stream 的操作不会修改原始数据源 |
| 惰性求值 | 中间操作不会立即执行,只有终端操作触发时才真正计算 |
| 可消费性 | 一个 Stream 只能被消费一次,用完即关闭 |
| 并行能力 | 可以轻松切换为并行流 parallelStream(),充分利用多核 CPU |
三、与传统集合处理的对比
| 对比维度 | 传统方式 | Stream 流 |
|---|---|---|
| 迭代方式 | 外部迭代(手动 for/while) | 内部迭代(API 自动处理) |
| 代码风格 | 命令式(告诉怎么做) | 声明式(告诉做什么) |
| 并行处理 | 需手动编写多线程代码 | parallelStream() 一键并行 |
| 链式调用 | 不支持 | 支持 .filter().map().collect() |
| 数据源修改 | 可能修改原数据 | 不修改原数据源 |
四、Stream 操作的三大阶段
数据源 → 中间操作 → 终端操作
(集合/数组) (filter/map/sorted...) (collect/forEach/count...)
4.1 获取 Stream 的 9 种方式
// 1. 从单列集合获取
List<String> list = Arrays.asList("a", "b", "c");
Stream<String> stream1 = list.stream(); // 顺序流
Stream<String> stream2 = list.parallelStream(); // 并行流
// 2. 从 Map 集合获取(间接)
Map<String, Integer> map = new HashMap<>();
Stream<String> keyStream = map.keySet().stream(); // 键流
Stream<Integer> valueStream = map.values().stream(); // 值流
Stream<Map.Entry<String, Integer>> entryStream = map.entrySet().stream(); // 键值对流
// 3. 从数组获取
String[] arr = {"x", "y", "z"};
Stream<String> stream3 = Arrays.stream(arr);
// 4. 直接创建零散数据
Stream<String> stream4 = Stream.of("Java", "Python", "C++");
Stream<Integer> stream5 = Stream.of(1, 2, 3, 4, 5);
// 5. 使用 Stream.builder()
Stream<String> stream6 = Stream.<String>builder()
.add("Hello")
.add("World")
.build();
// 6. 生成无限流(必须用 limit 截断)
Stream<Integer> iterStream = Stream.iterate(0, n -> n + 2).limit(10); // 0,2,4...
Stream<Double> generateStream = Stream.generate(Math::random).limit(5);
// 7. 基本类型流(避免装箱开销)
IntStream intStream = IntStream.of(1, 2, 3);
LongStream longStream = LongStream.range(0, 100); // [0, 100)
LongStream rangeClosed = LongStream.rangeClosed(0, 100); // [0, 100]
DoubleStream doubleStream = DoubleStream.of(1.0, 2.5, 3.8);
// 8. 从文件获取
try (Stream<String> lines = Files.lines(Paths.get("file.txt"))) {
lines.forEach(System.out::println);
}
// 9. 从字符串拆分获取
String str = "apple,banana,orange";
Stream<String> stream9 = Pattern.compile(",").splitAsStream(str);
五、中间操作(Intermediate Operations)
中间操作返回一个新的 Stream,可以链式调用,惰性执行。
5.1 常用中间方法一览
| 方法 | 作用 | 示例 |
|---|---|---|
| filter(Predicate) | 按条件过滤 | .filter(x -> x > 10) |
| map(Function) | 转换每个元素 | .map(String::toUpperCase) |
| flatMap(Function) | 将每个元素展开为多个元素 | .flatMap(s -> Arrays.stream(s.split(","))) |
| distinct() | 去重(依赖 equals/hashCode) | .distinct() |
| sorted() | 自然排序 | .sorted() |
| sorted(Comparator) | 自定义排序 | .sorted(Comparator.reverseOrder()) |
| limit(long n) | 截取前 n 个 | .limit(5) |
| skip(long n) | 跳过前 n 个 | .skip(3) |
| peek(Consumer) | 调试/查看每个元素 | .peek(System.out::println) |
| takeWhile(Predicate) | 从头取,直到条件为假(Java 9+) | .takeWhile(x -> x < 10) |
| dropWhile(Predicate) | 从头丢弃,直到条件为假(Java 9+) | .dropWhile(x -> x < 10) |
5.2 方法详解
filter ------ 过滤:保留满足条件的元素。
List<String> list = List.of("张三", "张无忌", "赵敏", "周芷若");
list.stream()
.filter(name -> name.startsWith("张")) // 只保留姓张的
.forEach(System.out::println);
// 输出:张三 张无忌
map ------ 转换:把流中每个元素映射成另一种形式(一对一)。
List<String> list = List.of("apple", "banana", "cherry");
list.stream()
.map(String::toUpperCase) // 全部转大写
.forEach(System.out::println);
// 输出:APPLE BANANA CHERRY
// 提取对象中的字段
students.stream()
.map(Student::getName) // Student → String
.forEach(System.out::println);
flatMap ------ 扁平映射:每个元素映射成多个元素,再"拍平"成一个流(一对多)。
// 把多个单词拆成单个字符
List<String> words = List.of("hello", "world");
words.stream()
.flatMap(word -> Arrays.stream(word.split("")))
.distinct()
.forEach(System.out::print);
// 输出:helowrd
// 嵌套列表扁平化
List<List<Integer>> nested = Arrays.asList(
Arrays.asList(1, 2),
Arrays.asList(3, 4, 5),
Arrays.asList(6)
);
List<Integer> flat = nested.stream()
.flatMap(List::stream)
.collect(Collectors.toList());
// 结果:[1, 2, 3, 4, 5, 6]
一句话区分:map 是一对一,flatMap 是一对多。
distinct ------ 去重 :底层依赖 equals() 和 hashCode(),自定义对象需重写这两个方法。
List<Integer> list = List.of(1, 2, 2, 3, 3, 3, 4);
list.stream().distinct().forEach(System.out::print);
// 输出:1234
sorted ------ 排序:
// 自然排序(元素必须实现 Comparable)
Stream.of(3, 1, 5, 2, 4).sorted().forEach(System.out::print);
// 输出:12345
// 自定义比较器(按字符串长度升序)
Stream.of("aaa", "b", "cc")
.sorted((a, b) -> a.length() - b.length())
.forEach(System.out::println);
// 输出:b cc aaa
// 方法引用降序
Stream.of(3, 1, 5).sorted(Comparator.reverseOrder());
limit & skip ------ 截取与跳过(常用于分页):
Stream.of(1, 2, 3, 4, 5).limit(3).forEach(System.out::print); // 123
Stream.of(1, 2, 3, 4, 5).skip(2).forEach(System.out::print); // 345
// 第 2 页,每页 2 条 → 跳过前 2 条,取 2 条
Stream.of(1, 2, 3, 4, 5).skip(2).limit(2).forEach(System.out::print);
// 输出:34
peek ------ 调试:不改变流内容,常用于观察每一步中间结果。
list.stream()
.filter(s -> s.length() > 3)
.peek(s -> System.out.println("过滤后:" + s))
.map(String::toUpperCase)
.peek(s -> System.out.println("映射后:" + s))
.collect(Collectors.toList());
5.3 链式调用示例
List<String> names = List.of("张三丰", "张无忌", "赵敏", "周芷若", "张三", "韦小宝");
List<String> result = names.stream()
.filter(name -> name.startsWith("张")) // 1. 过滤姓张的
.skip(1) // 2. 跳过第一个
.limit(2) // 3. 只取 2 个
.sorted((a, b) -> b.length() - a.length()) // 4. 按名字长度降序
.collect(Collectors.toList());
System.out.println(result); // [张无忌, 张三]
六、终端操作(Terminal Operations)
终端操作触发流水线执行,执行后 Stream 被消费关闭。
6.1 终端方法总览
| 方法 | 返回类型 | 作用 |
|---|---|---|
| forEach | void | 遍历每个元素(并行流不保证顺序) |
| forEachOrdered | void | 按遭遇顺序遍历(并行流也保证顺序) |
| collect | R | 将结果收集为集合/字符串等 |
| toList | List | 收集为不可变 List(Java 16+) |
| toArray | Object\[\] / A\[\] | 转为数组 |
| count | long | 统计元素个数 |
| reduce | Optional / T | 聚合计算(求和、求积等) |
| min / max | Optional | 最大/最小值 |
| anyMatch | boolean | 是否有任一元素匹配 |
| allMatch | boolean | 是否全部元素匹配 |
| noneMatch | boolean | 是否没有元素匹配 |
| findFirst | Optional | 返回第一个元素 |
| findAny | Optional | 返回任意一个元素(并行流更高效) |
| iterator | Iterator | 获取迭代器 |
6.2 关键方法详解
reduce ------ 归约(三种重载形式):
// ① 单参数:返回 Optional
Optional<Integer> sum1 = Stream.of(1, 2, 3).reduce((a, b) -> a + b);
// ② 双参数(带初始值):返回具体值
int sum2 = Stream.of(1, 2, 3).reduce(0, (a, b) -> a + b); // 6
// ③ 三参数(并行归约)
int result = Stream.of(1, 2, 3).parallel()
.reduce(0, (a, b) -> a + b, (a, b) -> a + b);
匹配类方法对比:
| 方法 | 匹配规则 |
|---|---|
| anyMatch | 至少一个匹配 |
| allMatch | 全部匹配 |
| noneMatch | 无一匹配 |
boolean hasAdult = persons.stream().anyMatch(p -> p.getAge() >= 18);
boolean allAdult = persons.stream().allMatch(p -> p.getAge() >= 18);
boolean noMinor = persons.stream().noneMatch(p -> p.getAge() < 18);
基础示例:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// 统计偶数个数
long count = numbers.stream().filter(n -> n % 2 == 0).count(); // 5
// 求和
int sum = numbers.stream().reduce(0, Integer::sum); // 55
// 判断是否存在大于 5 的数
boolean hasBig = numbers.stream().anyMatch(n -> n > 5); // true
七、Stream 的 10 种遍历方式
| 场景 | 推荐方式 |
|---|---|
| 简单打印 | forEach |
| 并行流需保证顺序 | forEachOrdered |
| 转换后遍历 | map + forEach |
| 调试查看中间结果 | peek(中间操作,需配终端操作) |
| 高性能批量 | parallelStream() + forEach |
// 1. forEach 最常用
list.stream().forEach(System.out::println);
// 2. forEachOrdered 并行流保证顺序
list.parallelStream().forEachOrdered(System.out::println);
// 3. collect 收集后再遍历
List<String> newList = list.stream()
.filter(name -> name.length() > 3)
.collect(Collectors.toList());
newList.forEach(System.out::println);
// 4. reduce 累积
int sum = list.stream().reduce(0, Integer::sum);
String result = list.stream().reduce("", String::concat);
// 5. 转数组后遍历
String[] array = list.stream().toArray(String[]::new);
Arrays.stream(array).forEach(System.out::println);
// 6. iterator 迭代器
Iterator<String> it = list.stream().iterator();
while (it.hasNext()) {
System.out.println(it.next());
}
// 7. Spliterator 高级遍历
Spliterator<String> spliterator = list.stream().spliterator();
spliterator.forEachRemaining(System.out::println);
// 8. map + forEach 转换后遍历
list.stream().map(String::toUpperCase).forEach(System.out::println);
// 9. peek 调试用(中间操作,必须配合终端操作才会执行)
list.stream()
.peek(name -> System.out.println("过滤前: " + name))
.filter(name -> name.length() > 3)
.forEach(System.out::println);
// 10. 并行流遍历
list.parallelStream().forEach(System.out::println); // 不保证顺序
list.parallelStream().forEachOrdered(System.out::println); // 保证顺序
八、Collectors 收集器详解
collect() 是最灵活的终端操作,配合 Collectors 工具类使用。
8.1 收集为集合
List<String> list = Arrays.asList("apple", "banana", "cherry", "date", "apple");
// 收集为 List
List<String> toList = list.stream().collect(Collectors.toList());
// 收集为 Set(自动去重)
Set<String> toSet = list.stream().collect(Collectors.toSet());
// 收集为 Map(注意重复 key 需指定合并策略)
Map<String, Integer> toMap = list.stream()
.distinct()
.collect(Collectors.toMap(
Function.identity(), // key: 元素本身
String::length // value: 元素长度
));
// toMap 遇重复 key 默认抛 IllegalStateException,必须传 mergeFunction
Map<Integer, String> map = Stream.of("Java", "Python", "Go")
.collect(Collectors.toMap(
String::length,
s -> s,
(oldVal, newVal) -> oldVal // 冲突时保留旧值
));
// 结果:{2=Go, 4=Java, 6=Python}
8.2 joining 字符串拼接
// 直接拼接
String s1 = Stream.of("A", "B", "C").collect(Collectors.joining()); // "ABC"
// 带分隔符
String s2 = Stream.of("Java", "Python", "Go")
.collect(Collectors.joining(", ")); // "Java, Python, Go"
// 带前后缀
String s3 = Stream.of("Java", "Python", "Go")
.collect(Collectors.joining(", ", "[", "]")); // "[Java, Python, Go]"
8.3 统计
// 计数
long count = Stream.of(1, 2, 3, 4, 5).collect(Collectors.counting()); // 5
// 求和
int sum = Stream.of(1, 2, 3, 4, 5)
.collect(Collectors.summingInt(Integer::intValue)); // 15
// 平均值
double avg = Stream.of(10, 20, 30)
.collect(Collectors.averagingInt(Integer::intValue)); // 20.0
// 一次性拿到统计信息
IntSummaryStatistics stats = list.stream()
.collect(Collectors.summarizingInt(String::length));
stats.getCount(); stats.getSum(); stats.getMax(); stats.getMin(); stats.getAverage();
8.4 maxBy / minBy
Optional<Integer> max = Stream.of(3, 7, 2, 9, 1)
.collect(Collectors.maxBy(Integer::compareTo)); // Optional[9]
Optional<String> minStr = Stream.of("apple", "banana", "cherry")
.collect(Collectors.minBy(Comparator.comparingInt(String::length)));
// Optional[apple](长度最短)
8.5 groupingBy 分组(重点)
类似 SQL 的 GROUP BY,按某个属性将元素分组。
// 按字符串长度分组
Map<Integer, List<String>> groups = Stream.of("Java", "Go", "Rust", "C", "Python")
.collect(Collectors.groupingBy(String::length));
// {1=[C], 2=[Go], 4=[Java, Rust], 6=[Python]}
// 分组 + 下游收集器:统计每组数量
Map<Integer, Long> countByLen = Stream.of("Java", "Go", "Rust", "C", "Python")
.collect(Collectors.groupingBy(
String::length,
Collectors.counting()
));
// {1=1, 2=1, 4=2, 6=1}
groupingBy 三种重载:
| 方法 | 说明 |
|---|---|
groupingBy(classifier) |
基础分组,value 默认 List |
groupingBy(classifier, downstream) |
指定下游收集器(counting、toSet 等) |
groupingBy(classifier, mapFactory, downstream) |
指定返回的 Map 类型(如 TreeMap) |
8.6 partitioningBy 分区
按布尔条件分为 true / false 两组,是 groupingBy 的特例。
// 按是否为偶数分区
Map<Boolean, List<Integer>> partitions = Stream.of(1, 2, 3, 4, 5, 6)
.collect(Collectors.partitioningBy(n -> n % 2 == 0));
// {false=[1, 3, 5], true=[2, 4, 6]}
// 分区 + 下游收集器
Map<Boolean, Long> countByPartition = Stream.of(1, 2, 3, 4, 5, 6)
.collect(Collectors.partitioningBy(n -> n % 2 == 0, Collectors.counting()));
// {false=3, true=3}
8.7 collectingAndThen 收集后转换
先收集,再对结果做一次转换,常用于包装为不可变集合。
// 收集为 List 后包装成不可变集合
List<String> unmodifiable = Stream.of("A", "B", "C")
.collect(Collectors.collectingAndThen(
Collectors.toList(),
Collections::unmodifiableList
));
// unmodifiable 调用 add/remove 会抛 UnsupportedOperationException
8.8 Collectors 速查表
| 方法 | 用途 | 返回类型 |
|---|---|---|
toList() |
收集到 List | List<T> |
toSet() |
收集到 Set(去重) | Set<T> |
toMap(k, v) |
收集到 Map | Map<K, V> |
toMap(k, v, merge) |
收集到 Map(含冲突策略) | Map<K, V> |
joining() |
字符串拼接 | String |
joining(delimiter) |
带分隔符拼接 | String |
joining(delimiter, prefix, suffix) |
带前后缀拼接 | String |
counting() |
计数 | Long |
summingInt() |
求和 | Integer |
averagingInt() |
求均值 | Double |
maxBy(comparator) |
取最大值 | Optional<T> |
minBy(comparator) |
取最小值 | Optional<T> |
groupingBy(classifier) |
分组 | Map<K, List<T>> |
groupingBy(classifier, downstream) |
分组 + 下游收集 | Map<K, D> |
partitioningBy(predicate) |
按布尔分区 | Map<Boolean, List<T>> |
collectingAndThen(c, finisher) |
收集后二次转换 | 取决于 finisher |
九、实战案例
案例 1:筛选及格学生并按分数降序
class Student {
String name;
int score;
// 构造方法、getter 省略
}
List<Student> students = Arrays.asList(
new Student("张三", 85),
new Student("李四", 58),
new Student("王五", 92),
new Student("赵六", 73)
);
List<String> topStudents = students.stream()
.filter(s -> s.getScore() >= 60) // 及格
.sorted((a, b) -> b.getScore() - a.getScore()) // 降序
.map(Student::getName) // 只要名字
.collect(Collectors.toList());
// 结果:[王五, 张三, 赵六]
案例 2:统计单词出现频率
List<String> words = Arrays.asList("java", "python", "java", "c++", "python", "java");
Map<String, Long> freq = words.stream()
.collect(Collectors.groupingBy(
Function.identity(),
Collectors.counting()
));
// 结果:{python=2, c++=1, java=3}
案例 3:基本类型流性能优化
// 处理 int / long / double 时优先使用基本类型流,避免装箱开销
int sum = IntStream.range(1, 100) // 1 到 99
.filter(n -> n % 2 == 0)
.sum(); // 2450
十、传统写法 vs Stream 写法
需求:从一个整数列表中筛选出偶数,排序后取前 3 个。
传统写法:
List<Integer> result = new ArrayList<>();
for (Integer n : numbers) {
if (n % 2 == 0) {
result.add(n);
}
}
Collections.sort(result);
List<Integer> top3 = result.subList(0, Math.min(3, result.size()));
Stream 写法:
List<Integer> top3 = numbers.stream()
.filter(n -> n % 2 == 0)
.sorted()
.limit(3)
.collect(Collectors.toList());
代码量减少约 60%,可读性和维护性大幅提升。
十一、注意事项
- Stream 不可复用 :一个 Stream 只能被消费一次,重复使用会抛出
IllegalStateException。 - 避免副作用 :不要在
forEach或peek中修改外部可变状态,保持函数式编程的纯粹性。 - 并行流谨慎使用 :
parallelStream()适合 CPU 密集型、数据量大的场景,但要注意线程安全问题。 - 空值处理 :配合
Optional使用findFirst/findAny等方法,避免 NPE。 - 基本类型流 :处理 int / long / double 时优先使用
IntStream/LongStream/DoubleStream,避免装箱开销。 - distinct 依赖重写 :自定义对象去重需重写
equals()和hashCode()。 - sorted 稳定性 :Java 的
sorted()是稳定排序,相等元素保持原顺序。 - toMap 重复 key:不传 mergeFunction 时遇到重复 key 会抛异常,务必指定合并策略。