JavaStream流详解
Java Stream 流详解
本章位置:第二阶段 Java 核心框架
前置知识:集合框架、Lambda 表达式、函数式接口
本章内容:Java Stream API
下一篇:MyBatis 基础
学习目标:掌握 Stream 的创建、筛选、映射、排序、去重、截取、聚合、收集、分组、分区、并行流等核心操作,并理解中间操作、终止操作、懒执行、流不可重复消费等底层规则。
一、Stream 是什么
Stream 是 Java 8 引入的重要 API。
它主要用于:
处理集合数据
例如:
筛选
转换
排序
去重
统计
分组
聚合
二、为什么需要 Stream
传统集合处理:
List<Student> result =
new ArrayList<>();
for (
Student student :
students
) {
if (
student.getScore()
>= 60
) {
result.add(
student
);
}
}
Stream:
List<Student> result =
students.stream()
.filter(
student ->
student.getScore()
>= 60
)
.toList();
三、Stream 的核心思想
Stream 可以理解成:
数据源
↓
一系列处理操作
↓
最终结果
例如:
students.stream()
.filter(...)
.map(...)
.sorted(...)
.toList();
可以理解:
学生集合
↓
过滤
↓
转换
↓
排序
↓
收集成列表
四、Stream 不是集合
非常重要:
Stream 本身不保存数据
它更像:
数据处理流水线
真正的数据仍然来自:
List
Set
数组
文件
其他数据源
五、Stream 通常不会修改原集合
例如:
List<Integer> numbers =
List.of(
1,
2,
3
);
List<Integer> result =
numbers.stream()
.map(
value ->
value * 2
)
.toList();
原集合:
1,2,3
结果:
2,4,6
一般情况下:
Stream 创建的是新的结果
六、Stream 基本使用流程
最典型:
1. 创建流
2. 中间操作
3. 终止操作
例如:
students.stream()
.filter(...)
.map(...)
.toList();
其中:
stream()
创建流
filter()
中间操作
map()
中间操作
toList()
终止操作
七、创建 Stream
集合:
list.stream();
并行流:
list.parallelStream();
数组:
Arrays.stream(
array
);
值:
Stream.of(
"A",
"B",
"C"
);
八、Collection.stream()
例如:
List<String> names =
List.of(
"张三",
"李四",
"王五"
);
Stream<String> stream =
names.stream();
九、Arrays.stream()
int[] numbers =
{
1,
2,
3
};
IntStream stream =
Arrays.stream(
numbers
);
十、Stream.of()
Stream<String> stream =
Stream.of(
"Java",
"MySQL",
"Spring"
);
十一、Stream.iterate()
可以生成规律数据。
例如:
Stream<Integer> stream =
Stream.iterate(
1,
value ->
value + 1
);
这是:
无限流
必须配合:
limit()
例如:
List<Integer> numbers =
Stream.iterate(
1,
value ->
value + 1
)
.limit(
10
)
.toList();
十二、Stream.generate()
Stream<Double> stream =
Stream.generate(
Math::random
);
也是无限流。
例如:
List<Double> values =
Stream.generate(
Math::random
)
.limit(
5
)
.toList();
十三、中间操作
常见中间操作:
filter
map
flatMap
distinct
sorted
peek
limit
skip
特点:
返回新的 Stream
十四、终止操作
常见:
forEach
toList
collect
count
reduce
findFirst
findAny
anyMatch
allMatch
noneMatch
min
max
特点:
真正触发 Stream 执行
十五、Stream 是懒执行
例如:
students.stream()
.filter(
student -> {
System.out.println(
student
.getName()
);
return true;
}
);
如果没有终止操作:
可能什么都不会执行
十六、为什么叫懒执行
Stream 的中间操作:
不会立刻处理全部数据
而是:
等终止操作出现后
才真正开始执行流水线
十七、filter()
用于:
筛选数据
接收:
Predicate<T>
例如:
List<Student> passed =
students.stream()
.filter(
student ->
student.getScore()
>= 60
)
.toList();
十八、多条件 filter
students.stream()
.filter(
student ->
student.getAge()
>= 18
)
.filter(
student ->
student.getScore()
>= 60
);
也可以:
.filter(
student ->
student.getAge()
>= 18
&&
student.getScore()
>= 60
)
十九、多个 filter 和一个复杂 filter
多个 filter:
可读性好
规则可以逐层拆分
一个 filter:
条件集中
实际根据:
代码可读性
选择。
二十、map()
用于:
把一个元素转换成另一个元素
接收:
Function<T,R>
例如:
List<String> names =
students.stream()
.map(
Student::getName
)
.toList();
二十一、map 改变元素类型
原来:
Stream<Student>
经过:
.map(
Student::getName
)
变成:
Stream<String>
二十二、map 做计算
List<Integer> doubled =
numbers.stream()
.map(
value ->
value * 2
)
.toList();
二十三、map 转 DTO
例如:
List<UserVO> voList =
users.stream()
.map(
user ->
new UserVO(
user.getId(),
user.getUsername()
)
)
.toList();
这是实际项目很常见的用法。
二十四、flatMap()
flatMap() 用于:
把嵌套结构展开
例如:
List<List<String>> data =
List.of(
List.of(
"A",
"B"
),
List.of(
"C",
"D"
)
);
如果:
data.stream()
得到:
Stream<List<String>>
二十五、flatMap 展开
List<String> result =
data.stream()
.flatMap(
List::stream
)
.toList();
结果:
A
B
C
D
二十六、map 和 flatMap 区别
map
一对一转换
flatMap
一对多转换并扁平化
例如:
Student
→
name
属于 map
而:
班级
→
多个学生
→
全部学生展开
属于 flatMap
二十七、字符串拆词案例
List<String> lines =
List.of(
"Java MySQL",
"Spring Redis"
);
展开单词:
List<String> words =
lines.stream()
.flatMap(
line ->
Arrays.stream(
line.split(
"\\s+"
)
)
)
.toList();
二十八、distinct()
去重:
List<Integer> result =
Stream.of(
1,
2,
2,
3,
3,
3
)
.distinct()
.toList();
结果:
1
2
3
二十九、自定义对象 distinct()
对于普通对象,
distinct() 依赖:
equals()
hashCode()
如果 Student 没正确重写:
equals/hashCode
去重结果可能不是你预期的。
三十、按某个字段去重
例如:
按 studentNo 去重
Stream 原生没有:
distinctByKey()
可以用:
Map
或辅助方法处理。
例如:
List<Student> result =
new ArrayList<>(
students.stream()
.collect(
Collectors.toMap(
Student::getStudentNo,
Function.identity(),
(
oldValue,
newValue
) ->
oldValue,
LinkedHashMap::new
)
)
.values()
);
三十一、sorted()
自然排序:
List<Integer> result =
numbers.stream()
.sorted()
.toList();
三十二、自定义排序
List<Student> result =
students.stream()
.sorted(
Comparator
.comparingInt(
Student::getAge
)
)
.toList();
三十三、降序
.sorted(
Comparator
.comparingInt(
Student::getScore
)
.reversed()
)
三十四、多字段排序
.sorted(
Comparator
.comparingInt(
Student::getScore
)
.reversed()
.thenComparing(
Student::getName
)
)
三十五、limit()
取前 N 个:
stream.limit(
10
);
例如成绩前 3:
List<Student> top3 =
students.stream()
.sorted(
Comparator
.comparingInt(
Student::getScore
)
.reversed()
)
.limit(
3
)
.toList();
三十六、skip()
跳过前 N 个:
stream.skip(
10
);
三十七、skip + limit 分页
例如:
int pageNum = 3;
int pageSize = 10;
List<Student> page =
students.stream()
.skip(
(long)
(pageNum - 1)
* pageSize
)
.limit(
pageSize
)
.toList();
注意:
这是内存分页
数据库大量数据分页:
应该在 SQL 中分页
不要把百万条数据查出来再 Stream 分页。
三十八、peek()
peek() 常用于:
调试流水线
例如:
students.stream()
.filter(
student ->
student.getScore()
>= 60
)
.peek(
student ->
System.out.println(
"过滤后:"
+ student
)
)
.map(
Student::getName
)
.toList();
三十九、不要把 peek 当主要业务操作
不推荐:
.peek(
student ->
database.save(
student
)
)
peek() 语义更偏:
观察元素
调试
业务副作用更适合:
明确的 forEach
普通循环
业务方法
四十、forEach()
终止操作。
students.stream()
.forEach(
System.out::println
);
集合本身也可以:
students.forEach(
System.out::println
);
如果不需要 Stream 其他操作,
直接集合:
forEach
更简单。
四十一、forEachOrdered()
并行流中:
forEach()
不一定保持原顺序。
需要按遇到顺序执行:
forEachOrdered()
四十二、count()
long count =
students.stream()
.filter(
student ->
student.getScore()
>= 60
)
.count();
四十三、min()
Optional<Student> min =
students.stream()
.min(
Comparator
.comparingInt(
Student::getScore
)
);
四十四、max()
Optional<Student> max =
students.stream()
.max(
Comparator
.comparingInt(
Student::getScore
)
);
四十五、为什么 min/max 返回 Optional
因为:
Stream 可能为空
空流:
没有最小值
也没有最大值
所以使用:
Optional
表达:
可能没有结果
四十六、findFirst()
Optional<Student> student =
students.stream()
.filter(
item ->
item.getScore()
>= 90
)
.findFirst();
四十七、findAny()
Optional<Student> student =
students.parallelStream()
.filter(...)
.findAny();
在并行流中:
可能更适合快速找到任意一个
四十八、anyMatch()
只要一个匹配:
boolean hasFail =
students.stream()
.anyMatch(
student ->
student.getScore()
< 60
);
四十九、allMatch()
全部匹配:
boolean allPassed =
students.stream()
.allMatch(
student ->
student.getScore()
>= 60
);
五十、noneMatch()
没有任何一个匹配:
boolean noFail =
students.stream()
.noneMatch(
student ->
student.getScore()
< 60
);
五十一、匹配操作有短路特性
例如:
anyMatch()
找到第一个满足条件后:
可以提前结束
不一定遍历全部元素。
类似:
findFirst
findAny
limit
都有短路特性。
五十二、reduce()
reduce() 用于:
把多个元素聚合成一个结果
例如求和:
int sum =
numbers.stream()
.reduce(
0,
(
a,
b
) ->
a + b
);
五十三、reduce 方法引用
int sum =
numbers.stream()
.reduce(
0,
Integer::sum
);
五十四、没有初始值的 reduce
Optional<Integer> result =
numbers.stream()
.reduce(
Integer::sum
);
因为空流:
没有结果
所以返回:
Optional
五十五、reduce 求最大值
Optional<Integer> max =
numbers.stream()
.reduce(
Integer::max
);
五十六、reduce 不适合所有场景
如果目标是:
收集成 List
Map
Set
分组
更适合:
collect()
而不是硬写复杂 reduce。
五十七、collect()
collect() 是 Stream 中最重要的终止操作之一。
主要用于:
把流中的元素收集到容器
五十八、Collectors.toList()
经典:
List<String> names =
students.stream()
.map(
Student::getName
)
.collect(
Collectors.toList()
);
Java 16+ 还可以:
.toList()
五十九、toList() 和 Collectors.toList() 区别
stream.toList()
返回列表:
通常不可修改
而:
collect(
Collectors.toList()
)
返回实现类型:
规范不保证具体类型
通常可修改,但不要依赖具体实现细节。
六十、如果明确需要可修改 List
可以:
List<Student> result =
students.stream()
.filter(...)
.collect(
Collectors.toCollection(
ArrayList::new
)
);
这样明确得到:
ArrayList
六十一、Collectors.toSet()
Set<String> majors =
students.stream()
.map(
Student::getMajor
)
.collect(
Collectors.toSet()
);
六十二、toCollection()
LinkedHashSet<String> majors =
students.stream()
.map(
Student::getMajor
)
.collect(
Collectors.toCollection(
LinkedHashSet::new
)
);
六十三、Collectors.joining()
字符串拼接:
String names =
students.stream()
.map(
Student::getName
)
.collect(
Collectors.joining(
", "
)
);
结果:
张三, 李四, 王五
六十四、joining 前缀后缀
String result =
students.stream()
.map(
Student::getName
)
.collect(
Collectors.joining(
", ",
"[",
"]"
)
);
结果:
[张三, 李四, 王五]
六十五、Collectors.toMap()
例如:
studentNo → Student
Map<String, Student> map =
students.stream()
.collect(
Collectors.toMap(
Student::getStudentNo,
Function.identity()
)
);
六十六、Function.identity()
Function.identity()
等价:
value ->
value
所以:
Student::getStudentNo
作为 key,
Student 本身作为 value。
六十七、toMap 重复 key 问题
如果两个 Student:
studentNo 相同
直接:
Collectors.toMap(...)
会抛:
IllegalStateException
六十八、指定冲突处理
Map<String, Student> map =
students.stream()
.collect(
Collectors.toMap(
Student::getStudentNo,
Function.identity(),
(
oldValue,
newValue
) ->
oldValue
)
);
表示:
重复时保留旧值
六十九、保留新值
(
oldValue,
newValue
) ->
newValue
七十、指定 Map 类型
Map<String, Student> map =
students.stream()
.collect(
Collectors.toMap(
Student::getStudentNo,
Function.identity(),
(
oldValue,
newValue
) ->
oldValue,
LinkedHashMap::new
)
);
七十一、groupingBy()
分组:
Map<String, List<Student>>
group =
students.stream()
.collect(
Collectors.groupingBy(
Student::getMajor
)
);
结果:
软件工程
→ 多个学生
人工智能
→ 多个学生
七十二、groupingBy + counting()
统计每个专业人数:
Map<String, Long> counts =
students.stream()
.collect(
Collectors.groupingBy(
Student::getMajor,
Collectors.counting()
)
);
七十三、groupingBy + mapping()
按专业分组,
只保留姓名:
Map<String, List<String>>
namesByMajor =
students.stream()
.collect(
Collectors.groupingBy(
Student::getMajor,
Collectors.mapping(
Student::getName,
Collectors.toList()
)
)
);
七十四、groupingBy + averagingInt()
Map<String, Double>
avgScore =
students.stream()
.collect(
Collectors.groupingBy(
Student::getMajor,
Collectors.averagingInt(
Student::getScore
)
)
);
七十五、groupingBy 多级分组
先按专业,
再按性别:
Map<String,
Map<String,
List<Student>>>
result =
students.stream()
.collect(
Collectors.groupingBy(
Student::getMajor,
Collectors.groupingBy(
Student::getGender
)
)
);
七十六、partitioningBy()
分区:
只能分成 true / false 两组
例如:
Map<Boolean, List<Student>>
result =
students.stream()
.collect(
Collectors.partitioningBy(
student ->
student.getScore()
>= 60
)
);
七十七、groupingBy 和 partitioningBy 区别
groupingBy
可以分很多组
partitioningBy
固定 true / false 两组
七十八、统计汇总
IntSummaryStatistics statistics =
students.stream()
.mapToInt(
Student::getScore
)
.summaryStatistics();
七十九、summaryStatistics()
可以直接得到:
count
sum
min
max
average
例如:
statistics.getCount();
statistics.getSum();
statistics.getMin();
statistics.getMax();
statistics.getAverage();
八十、mapToInt()
普通:
Stream<Student>
经过:
mapToInt(
Student::getScore
)
变成:
IntStream
八十一、为什么有 IntStream
避免大量:
Integer 装箱拆箱
并提供:
sum
average
summaryStatistics
等数值专用操作。
八十二、IntStream
常见:
IntStream.range(
1,
10
);
生成:
1~9
八十三、rangeClosed()
IntStream.rangeClosed(
1,
10
);
生成:
1~10
八十四、IntStream 求和
int sum =
IntStream
.rangeClosed(
1,
100
)
.sum();
八十五、average()
OptionalDouble average =
students.stream()
.mapToInt(
Student::getScore
)
.average();
八十六、boxed()
把:
IntStream
转回:
Stream<Integer>
List<Integer> values =
IntStream
.rangeClosed(
1,
10
)
.boxed()
.toList();
八十七、Stream 只能消费一次
例如:
Stream<String> stream =
names.stream();
stream.forEach(
System.out::println
);
之后再:
stream.count();
会报:
stream has already been operated upon or closed
八十八、为什么只能消费一次
Stream:
代表一次数据处理流水线
终止后:
流水线已经结束
想再次处理:
重新创建 Stream
八十九、正确做法
不要:
Stream<Student> stream =
students.stream();
然后反复消费。
应该:
students.stream()
.filter(...)
.toList();
students.stream()
.map(...)
.toList();
九十、操作顺序很重要
例如:
students.stream()
.sorted(...)
.filter(...)
通常排序需要处理:
全部元素
如果先 filter:
students.stream()
.filter(...)
.sorted(...)
可能让参与排序的数据更少。
九十一、推荐尽早过滤
一般:
filter
尽量靠前
减少后续:
map
sorted
复杂计算
处理的数据量。
但前提:
不改变业务语义
九十二、短路操作优化
例如:
students.stream()
.filter(...)
.findFirst();
找到第一个后:
无需继续处理全部
九十三、Stream 的执行是流水线式
例如:
Stream.of(
1,
2,
3,
4
)
.filter(
value -> {
System.out.println(
"filter "
+ value
);
return value
% 2
== 0;
}
)
.map(
value -> {
System.out.println(
"map "
+ value
);
return value * 10;
}
)
.findFirst();
可能执行:
filter 1
filter 2
map 2
然后:
直接结束
不是:
先 filter 完所有
再 map 完所有
九十四、这叫垂直处理
Stream 很多操作会:
一个元素一个元素穿过流水线
而不是传统想象的:
每一步先生成完整新集合
这也是 Stream 的重要特性。
九十五、有状态和无状态中间操作
简单理解:
无状态:
filter
map
处理一个元素时:
不需要知道其他元素
有状态:
sorted
distinct
往往需要:
观察更多甚至全部元素
九十六、并行流
students.parallelStream()
会尝试:
并行处理数据
九十七、parallel()
普通流:
students.stream()
.parallel()
也可转成并行流。
九十八、sequential()
并行流可以:
stream.sequential()
恢复顺序流。
九十九、并行流不是一定更快
并行会产生:
任务拆分
线程调度
结果合并
上下文开销
小数据:
可能更慢
一百、并行流适合什么情况
更可能适合:
数据量大
任务 CPU 密集
操作独立
没有共享可变状态
拆分成本低
一百零一、并行流不适合什么
例如:
数据库访问
网络请求
共享集合修改
顺序强依赖
很小的数据量
不要看到:
parallelStream
就认为:
自动提速
一百零二、并行流共享变量危险
错误:
List<String> result =
new ArrayList<>();
students.parallelStream()
.forEach(
student ->
result.add(
student.getName()
)
);
ArrayList:
线程不安全
可能出问题。
一百零三、正确方式
直接:
List<String> result =
students.parallelStream()
.map(
Student::getName
)
.toList();
让 Stream:
自己负责结果合并
一百零四、副作用
Stream 中应尽量减少:
修改外部变量
修改共享对象
数据库写入
网络调用
这样的:
副作用
一百零五、为什么减少副作用
因为函数式代码更希望:
输入确定
↓
输出确定
更容易:
理解
测试
并行化
一百零六、Stream 和 Optional
例如:
Optional<Student> top =
students.stream()
.max(
Comparator
.comparingInt(
Student::getScore
)
);
处理:
top.ifPresent(
System.out::println
);
一百零七、orElse()
Student student =
optional.orElse(
defaultStudent
);
一百零八、orElseGet()
Student student =
optional.orElseGet(
Student::new
);
一百零九、orElse 和 orElseGet 的区别
orElse():
默认值表达式会先计算
orElseGet():
只有 Optional 为空时
才调用 Supplier
如果默认对象创建昂贵:
更适合 orElseGet
一百一十、orElseThrow()
Student student =
optional.orElseThrow(
() ->
new IllegalArgumentException(
"学生不存在"
)
);
一百一十一、Stream 中 null 的问题
例如:
students.stream()
.map(
Student::getName
)
如果 student 本身为 null:
NullPointerException
一百一十二、过滤 null
stream.filter(
Objects::nonNull
);
一百一十三、属性可能为 null
例如:
.map(
Student::getName
)
.map(
String::toUpperCase
)
如果 name 为 null:
NPE
需要根据业务:
filter null
默认值
Optional
处理。
一百一十四、Stream.ofNullable()
Java 9+:
Stream.ofNullable(
value
);
如果 value:
null
得到:
空流
否则:
单元素流
一百一十五、flatMap + Optional
Java 9+ Optional 有:
stream()
可以:
optionals.stream()
.flatMap(
Optional::stream
)
自动去掉:
空 Optional
一百一十六、综合案例:学生统计
假设:
List<Student> students;
需求:
筛选及格学生
按成绩降序
取前 3 名
得到姓名
一百一十七、完整 Stream
List<String> topNames =
students.stream()
.filter(
student ->
student.getScore()
>= 60
)
.sorted(
Comparator
.comparingInt(
Student::getScore
)
.reversed()
)
.limit(
3
)
.map(
Student::getName
)
.toList();
一百一十八、需求拆解方式
先不要急着写链式代码。
先写:
数据源是什么?
筛选条件是什么?
要不要转换?
要不要排序?
要不要截取?
最后要什么结果?
然后:
一步一步拼 Stream
一百一十九、综合案例:按专业统计人数
Map<String, Long> result =
students.stream()
.collect(
Collectors.groupingBy(
Student::getMajor,
Collectors.counting()
)
);
一百二十、综合案例:每个专业最高分学生
一种思路:
Map<String,
Optional<Student>>
result =
students.stream()
.collect(
Collectors.groupingBy(
Student::getMajor,
Collectors.maxBy(
Comparator
.comparingInt(
Student::getScore
)
)
)
);
一百二十一、如果不想要 Optional
可以继续:
Map<String, Student> result =
students.stream()
.collect(
Collectors.toMap(
Student::getMajor,
Function.identity(),
(
a,
b
) ->
a.getScore()
>= b.getScore()
? a
: b
)
);
一百二十二、综合案例:专业平均分排名
Map<String, Double> avgMap =
students.stream()
.collect(
Collectors.groupingBy(
Student::getMajor,
Collectors.averagingInt(
Student::getScore
)
)
);
然后:
List<Map.Entry<String, Double>>
ranking =
avgMap.entrySet()
.stream()
.sorted(
Map.Entry
.<String, Double>
comparingByValue()
.reversed()
)
.toList();
一百二十三、综合案例:单词频率统计
List<String> words =
List.of(
"java",
"mysql",
"java",
"spring",
"java",
"mysql"
);
统计:
Map<String, Long> counts =
words.stream()
.collect(
Collectors.groupingBy(
Function.identity(),
Collectors.counting()
)
);
一百二十四、按频率排序
List<Map.Entry<String, Long>>
ranking =
counts.entrySet()
.stream()
.sorted(
Map.Entry
.<String, Long>
comparingByValue()
.reversed()
)
.toList();
一百二十五、综合案例:订单总金额
假设:
List<Order> orders;
BigDecimal total =
orders.stream()
.map(
Order::getAmount
)
.reduce(
BigDecimal.ZERO,
BigDecimal::add
);
金额推荐:
BigDecimal
一百二十六、不要用 double 算金额
不推荐:
.mapToDouble(
Order::getAmount
)
.sum();
如果金额是业务货币,
应该:
BigDecimal
一百二十七、综合案例:去重专业并排序
List<String> majors =
students.stream()
.map(
Student::getMajor
)
.filter(
Objects::nonNull
)
.distinct()
.sorted()
.toList();
一百二十八、综合案例:嵌套集合展开
班级:
class Classroom {
private List<Student>
students;
}
所有学生:
List<Student> all =
classrooms.stream()
.flatMap(
classroom ->
classroom
.getStudents()
.stream()
)
.toList();
一百二十九、常见错误:流消费两次
错误:
Stream<Student> stream =
students.stream();
stream.count();
stream.toList();
第二次会失败。
一百三十、常见错误:忘记终止操作
students.stream()
.filter(
student -> {
System.out.println(
student
);
return true;
}
);
可能没有任何输出。
因为:
没有终止操作
一百三十一、常见错误:toMap 重复 key
Collectors.toMap(
Student::getMajor,
Function.identity()
)
如果多个学生专业一样:
key 重复
会异常。
一百三十二、常见错误:parallelStream 修改 ArrayList
错误:
List<String> result =
new ArrayList<>();
students.parallelStream()
.forEach(
student ->
result.add(
student.getName()
)
);
存在:
线程安全问题
一百三十三、常见错误:Stream 过度链式
不推荐:
stream
.filter(...)
.map(...)
.flatMap(...)
.filter(...)
.peek(...)
.map(...)
.sorted(...)
.collect(...);
如果逻辑难理解:
应该拆方法
一百三十四、常见错误:Stream 代替所有循环
有些逻辑:
需要 break
需要复杂状态
需要多步副作用
需要频繁异常处理
普通:
for
可能反而更清晰。
Stream 不是:
循环的绝对替代品
一百三十五、常见错误:数据库数据全查出来再 Stream
例如:
数据库 100 万条数据
不要:
SELECT 全部
↓
Stream filter
↓
Stream sorted
↓
limit
应该尽量让:
数据库 WHERE
ORDER BY
LIMIT
GROUP BY
完成。
一百三十六、数据库和 Stream 各自擅长什么
数据库擅长:
大数据筛选
排序
聚合
分页
索引查询
Stream 擅长:
内存中对象处理
业务转换
二次加工
一百三十七、常见错误:peek 做业务写操作
不要:
.peek(
order ->
orderDao.update(
order
)
)
peek 更适合:
观察
调试
一百三十八、常见错误:忽略 null
例如:
.map(
Student::getMajor
)
.distinct()
.sorted()
如果 major 为 null:
sorted()
可能出问题
可先:
.filter(
Objects::nonNull
)
一百三十九、Stream 调试方法
可以临时:
.peek(
value ->
System.out.println(
"当前值:"
+ value
)
)
但调试完成后:
及时清理无意义 peek
一百四十、IDEA 调试 Stream
IDEA 支持:
Debug
断点
Evaluate Expression
部分版本还能显示:
Stream Trace
如果当前 IDEA 版本提供:
Trace Current Stream Chain
可以查看:
每一步 Stream 结果
一百四十一、Stream Trace 的意义
复杂链:
students.stream()
.filter(...)
.map(...)
.sorted(...)
.toList();
可以帮助观察:
原数据
filter 后
map 后
sorted 后
一百四十二、练习题 1:筛选偶数
数据:
1~20
使用:
filter()
筛选偶数。
一百四十三、练习题 2:平方
对:
1,2,3,4,5
使用:
map()
得到:
1,4,9,16,25
一百四十四、练习题 3:去重
数据:
1,1,2,2,3,3
使用:
distinct()
一百四十五、练习题 4:学生及格名单
要求:
score >= 60
输出姓名。
使用:
filter
map
toList
一百四十六、练习题 5:成绩前三名
要求:
成绩降序
取前三
使用:
sorted
limit
一百四十七、练习题 6:统计专业人数
使用:
groupingBy
counting
一百四十八、练习题 7:分组姓名
结果:
专业
→
学生姓名 List
使用:
groupingBy
mapping
一百四十九、练习题 8:及格与不及格分区
使用:
partitioningBy
一百五十、练习题 9:单词频率
统计:
Java
MySQL
Spring
出现次数。
一百五十一、练习题 10:嵌套 List 展开
List<List<Integer>>
转:
List<Integer>
使用:
flatMap
一百五十二、练习题 11:金额求和
使用:
BigDecimal
reduce
计算订单总金额。
一百五十三、练习题 12:综合学生统计
完成:
1. 总人数
2. 及格人数
3. 平均分
4. 最高分
5. 最低分
6. 各专业人数
7. 各专业平均分
8. 成绩前三名
9. 所有专业去重排序
一百五十四、Stream 核心中间操作
必须掌握:
filter
map
flatMap
distinct
sorted
limit
skip
peek
一百五十五、Stream 核心终止操作
必须掌握:
forEach
toList
collect
count
min
max
findFirst
anyMatch
allMatch
noneMatch
reduce
一百五十六、Collectors 必须掌握
toList
toSet
toMap
joining
groupingBy
partitioningBy
counting
mapping
averagingInt
toCollection
一百五十七、数值流必须掌握
IntStream
LongStream
DoubleStream
mapToInt
sum
average
summaryStatistics
range
rangeClosed
一百五十八、必须回答的问题
学完后应该能回答:
1. Stream 是什么?
2. Stream 和集合有什么区别?
3. 什么是中间操作?
4. 什么是终止操作?
5. 为什么 Stream 是懒执行?
6. filter 和 map 有什么区别?
7. map 和 flatMap 有什么区别?
8. distinct 对对象为什么依赖 equals/hashCode?
9. Stream 为什么只能消费一次?
10. reduce 和 collect 有什么区别?
11. groupingBy 和 partitioningBy 有什么区别?
12. toMap 重复 key 怎么处理?
13. parallelStream 一定更快吗?
14. 为什么并行流不建议修改共享 ArrayList?
15. Stream 和数据库查询怎么分工?
一百五十九、Stream 知识结构
Stream
│
├─ 创建
│ ├─ Collection.stream
│ ├─ Arrays.stream
│ ├─ Stream.of
│ ├─ iterate
│ └─ generate
│
├─ 中间操作
│ ├─ filter
│ ├─ map
│ ├─ flatMap
│ ├─ distinct
│ ├─ sorted
│ ├─ limit
│ ├─ skip
│ └─ peek
│
├─ 终止操作
│ ├─ forEach
│ ├─ count
│ ├─ min/max
│ ├─ findFirst
│ ├─ match
│ ├─ reduce
│ └─ collect
│
├─ Collectors
│ ├─ toList
│ ├─ toSet
│ ├─ toMap
│ ├─ joining
│ ├─ groupingBy
│ └─ partitioningBy
│
├─ 数值流
│ ├─ IntStream
│ ├─ sum
│ ├─ average
│ └─ summaryStatistics
│
└─ 并行流
├─ parallelStream
├─ parallel
└─ sequential
一百六十、从 Stream 到 MyBatis
到这里:
Lambda
+
Stream
已经学完。
你现在已经能熟练处理:
Java 内存中的对象集合
下一篇进入:
MyBatis 基础
开始解决另一个核心问题:
Java 对象
和
数据库表
如何高效映射
后面会学习:
Mapper
SqlSession
配置文件
Mapper XML
参数绑定
结果映射
动态 SQL
一百六十一、本章总结
Stream 最核心的一句话:
Stream 是对数据源进行声明式、函数式处理的一条流水线。
标准思路:
数据源
↓
stream()
↓
filter
↓
map
↓
sorted
↓
limit
↓
collect / toList
最重要的三个概念:
中间操作
终止操作
懒执行
同时一定记住:
Stream 不保存数据
Stream 通常不修改原集合
Stream 只能消费一次
并行流不是自动加速器
复杂业务不要为了 Stream 而 Stream
掌握 Stream 后,
Java 8 以后最核心的:
Lambda + Stream
函数式集合处理能力就基本建立完成。
下一篇进入:
MyBatis 基础