Java8 Stream流全套实战教程|从原理、常用操作到避坑,一篇彻底吃透
我们是由枫哥组建的IT技术团队,成立于2017年,致力于帮助IT从业者提供实力,成功入职理想企业,我们提供一对一学习辅导,由知名大厂导师指导,分享Java技术、参与项目实战等服务,并为学员定制职业规划,全面提升竞争力,过去8年,我们已成功帮助数千名求职者拿到满意的Offer:IT枫斗者、IT枫斗者-Java面试突击。
前言
Java8 推出的 Stream API,是 Java 集合编程的重要分水岭,彻底告别了传统繁琐的 for 循环遍历模式,开启了声明式编程时代。
很多人觉得 Stream 只是“代码更短”,但它的核心价值远不止于此:将数据的筛选、转换、分组、聚合等操作,以流水线的方式直观表达,代码语义清晰、可读性极强、易于维护。
先通过一组对比,直观感受 Stream 的优势:
传统 For 循环写法
List<String> result = new ArrayList<>();
for (String name : names) {
if (name.length() > 3) {
result.add(name.toUpperCase());
}
}
Stream 流式写法
List<String> result = names.stream()
.filter(name -> name.length() > 3)
.map(String::toUpperCase)
.collect(Collectors.toList());
代码不再是机械的循环逻辑,而是直白描述数据处理规则:筛选、转换、收集,流水线语义一目了然。
一、Stream 到底是什么?(核心原理)
重点结论:Stream 不是集合、不存储数据,它是数据源的一次数据处理流水线,仅负责数据的计算、流转、处理。
一条完整的 Stream 流水线,固定由三部分组成:
List<String> result = names.stream() // 1. 数据源:创建流
.filter(name -> name.length() > 3) // 2. 中间操作:链式处理数据
.map(String::toUpperCase) // 2. 中间操作:链式处理数据
.collect(Collectors.toList()); // 3. 终止操作:触发执行、收集结果
核心特性:延迟执行
所有中间操作(filter/map/sorted等)仅做逻辑定义,不会立即执行。只有遇到**终止操作(collect/forEach/count等)**时,整条流水线才会一次性触发执行,极大提升执行效率。
二、Stream 常见创建方式
日常开发中,99% 的场景都是以下 4 种创建方式,简单好记、全覆盖。
1. 集合创建流(最常用)
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
// 普通串行流
Stream<Integer> stream = numbers.stream();
// 并行流(多线程处理,慎用)
Stream<Integer> parallelStream = numbers.parallelStream();
2. 数组创建流
String[] words = {"Java", "Stream", "Lambda"};
Stream<String> stream = Arrays.stream(words);
3. 快速创建少量元素流
Stream<String> stream = Stream.of("Java", "Spring", "MySQL");
4. 创建无限流(需配合 limit 截断)
无限流必须搭配 limit 使用,否则会无限循环执行!
// 生成 1、2、3、4、5... 无限自增序列
Stream<Integer> numbers = Stream.iterate(1, n -> n + 1);
// 截取前5个元素
numbers.limit(5).forEach(System.out::println);
输出结果:1 2 3 4 5
三、Stream 核心中间操作(业务高频)
中间操作特点:返回 Stream 对象,支持链式调用,延迟执行。
1. filter:数据筛选
接收 Predicate 断言,保留返回 true 的元素,过滤不符合条件数据。
// 筛选偶数
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
List<Integer> evenNumbers = numbers.stream()
.filter(n -> n % 2 == 0)
.collect(Collectors.toList());
// 输出:[2, 4, 6]
// 业务场景:筛选成年用户
List<User> adults = users.stream()
.filter(user -> user.getAge() >= 18)
.collect(Collectors.toList());
2. map:数据转换(一对一)
接收Function 函数,实现类型转换、字段提取、数据加工,一对一转换元素。
// 字符串转大写
List<String> names = Arrays.asList("alice", "bob", "charlie");
List<String> upperNames = names.stream()
.map(String::toUpperCase)
.collect(Collectors.toList());
// 业务高频:提取对象字段
List<String> userNameList = users.stream()
.map(User::getName)
.collect(Collectors.toList());
3. flatMap:扁平化处理(一对多)
核心区别:map 是一对一转换,flatMap 是一对多+摊平嵌套结构,专门处理二维集合、字符串拆分等嵌套场景。
// 二维列表转一维列表
List<List<String>> groups = Arrays.asList(
Arrays.asList("Java", "Spring"),
Arrays.asList("MySQL", "Redis")
);
List<String> skills = groups.stream()
.flatMap(List::stream)
.collect(Collectors.toList());
// 输出:[Java, Spring, MySQL, Redis]
// 字符串拆分摊平
List<String> lines = Arrays.asList("java stream", "spring boot");
List<String> words = lines.stream()
.flatMap(line -> Arrays.stream(line.split(" ")))
.collect(Collectors.toList());
// 输出:[java, stream, spring, boot]
4. distinct:元素去重
基本类型/字符串可直接去重,自定义对象去重依赖 equals() 和 hashCode() 方法,未重写会去重失效。
List<Integer> numbers = Arrays.asList(1, 2, 2, 3, 3, 3);
List<Integer> result = numbers.stream()
.distinct()
.collect(Collectors.toList());
// 输出:[1, 2, 3]
5. sorted:数据排序
分为自然排序和自定义比较器排序,属于有状态中间操作,需要缓存数据,性能低于 filter/map。
// 基本类型自然升序
List<Integer> sortedNumbers = Arrays.asList(5, 1, 3, 2, 4).stream()
.sorted()
.collect(Collectors.toList());
// 对象升序排序(按年龄)
List<User> sortedUserAsc = users.stream()
.sorted(Comparator.comparing(User::getAge))
.collect(Collectors.toList());
// 对象降序排序
List<User> sortedUserDesc = users.stream()
.sorted(Comparator.comparing(User::getAge).reversed())
.collect(Collectors.toList());
6. limit & skip:数据截取
常用于简易内存分页,注意:大数据量分页优先用数据库分页,内存分页性能极差。
List<Integer> numbers = Arrays.asList(1,2,3,4,5,6);
// 取前3个元素
List<Integer> firstThree = numbers.stream().limit(3).collect(Collectors.toList());
// 跳过前3个元素
List<Integer> afterThree = numbers.stream().skip(3).collect(Collectors.toList());
// 简易分页示例
int pageNo = 2;
int pageSize = 10;
List<User> pageData = users.stream()
.skip((long) (pageNo - 1) * pageSize)
.limit(pageSize)
.collect(Collectors.toList());
7. peek:调试偷看元素
仅用于开发调试,查看流水线中元素状态,禁止用于业务逻辑处理、修改外部状态。
List<String> result = names.stream()
.filter(name -> name.length() > 3)
.peek(name -> System.out.println("筛选后元素:" + name))
.map(String::toUpperCase)
.collect(Collectors.toList());
四、Stream 核心终止操作(触发执行)
终止操作执行后,Stream 流水线结束,流不可复用。
1. forEach / forEachOrdered:遍历
forEach:普通遍历,并行流不保证顺序forEachOrdered:保证遍历顺序,适配并行流
// 普通遍历
names.stream().forEach(System.out::println);
// 并行流有序遍历
names.parallelStream().forEachOrdered(System.out::println);
开发规范:遍历仅用于打印、调用方法等动作,生成新集合优先用 map/filter+collect。
2. collect:结果收集(最核心)
支持收集为 List、Set、Map、分组集合等,适配所有结果场景。
// 收集为 List
List<String> list = names.stream().collect(Collectors.toList());
// 收集为 Set(自动去重)
Set<String> set = names.stream().collect(Collectors.toSet());
// 收集为 Map(避坑:处理key重复)
Map<String, Integer> userMap = users.stream()
.collect(Collectors.toMap(
User::getName,
User::getAge,
(oldVal, newVal) -> newVal // key重复保留新值
));
3. groupingBy:分组统计(业务神器)
日常业务分组、统计场景必备,支持分组、计数、聚合。
// 按年龄分组
Map<Integer, List<User>> userGroupByAge = users.stream()
.collect(Collectors.groupingBy(User::getAge));
// 按年龄分组统计人数
Map<Integer, Long> userCountByAge = users.stream()
.collect(Collectors.groupingBy(
User::getAge,
Collectors.counting()
));
4. 聚合操作:count、max、min、reduce
// 统计成年用户数量
long adultCount = users.stream().filter(u -> u.getAge() >= 18).count();
// 获取年龄最大的用户(返回Optional,避免空指针)
Optional<User> oldestUser = users.stream()
.max(Comparator.comparing(User::getAge));
// reduce 归并求和
List<Integer> nums = Arrays.asList(1,2,3,4);
int sum = nums.stream().reduce(0, Integer::sum); // 结果:10
5. 匹配与查询:anyMatch、allMatch、findFirst
具备短路特性,匹配到结果立即终止遍历,性能极高。
// 是否存在成年人
boolean hasAdult = users.stream().anyMatch(u -> u.getAge() >= 18);
// 是否全部成年
boolean allAdult = users.stream().allMatch(u -> u.getAge() >= 18);
// 查找第一个成年人
Optional<User> firstAdult = users.stream()
.filter(u -> u.getAge() >= 18)
.findFirst();
五、数值流:IntStream/LongStream/DoubleStream
专门处理基本数据类型,避免装箱拆箱损耗,支持快速数值统计。
// 1-100求和
int sum = IntStream.rangeClosed(1, 100).sum();
// 对象流转数值流求和
int totalAge = users.stream().mapToInt(User::getAge).sum();
// 批量统计:最大、最小、平均值
IntSummaryStatistics stats = users.stream()
.mapToInt(User::getAge)
.summaryStatistics();
System.out.println("最大值:" + stats.getMax());
System.out.println("最小值:" + stats.getMin());
System.out.println("平均值:" + stats.getAverage());
六、Stream 延迟执行原理(核心面试点)
Stream 并非分步执行所有操作,而是将所有中间操作组装成一条流水线,单个元素一次性走完所有流程,搭配短路操作极大优化性能。
List<String> result = Arrays.asList("a", "bb", "ccc", "dddd").stream()
.filter(s -> {
System.out.println("filter: " + s);
return s.length() > 1;
})
.map(s -> {
System.out.println("map: " + s);
return s.toUpperCase();
})
.limit(2)
.collect(Collectors.toList());
执行输出:
filter: a
filter: bb
map: bb
filter: ccc
map: ccc
[BB, CCC]
可以看到:达到 limit(2) 后,后续元素不再处理,完美体现延迟执行+短路优化特性。
七、并行流:不是万能加速器!
并行流通过多线程拆分任务处理,有线程调度成本,并非越快越好。
适合并行流的场景
- 数据量大、单元素处理逻辑耗时
- 无共享可变状态、线程安全
- 数据源易拆分(ArrayList、数组、数值区间)
不适合并行流的场景
- 数据量小(线程调度成本大于计算收益)
- 强顺序依赖、I/O 密集型任务
- 存在共享变量修改操作
并行流正确写法(规避线程不安全)
// 错误:并行修改普通集合,线程不安全
List<Integer> badList = new ArrayList<>();
IntStream.rangeClosed(1, 1000).parallel().forEach(badList::add);
// 正确:通过collect收集,线程安全
List<Integer> goodList = IntStream.rangeClosed(1, 1000)
.parallel()
.boxed()
.collect(Collectors.toList());
八、业务实战:订单场景综合案例
基于订单类,实现日常高频统计、筛选、转换业务场景:
// 订单实体类
class Order {
private String orderNo;
private String userId;
private BigDecimal amount;
private String status;
// 构造器、getter省略
}
// 1. 统计已支付订单总金额
BigDecimal totalPaid = orders.stream()
.filter(o -> "PAID".equals(o.getStatus()))
.map(Order::getAmount)
.reduce(BigDecimal.ZERO, BigDecimal::add);
// 2. 按用户统计订单数量
Map<String, Long> userOrderCount = orders.stream()
.collect(Collectors.groupingBy(Order::getUserId, Collectors.counting()));
// 3. 获取金额最高的订单
Optional<Order> maxAmountOrder = orders.stream()
.max(Comparator.comparing(Order::getAmount));
// 4. 拼接所有订单号
String orderNoStr = orders.stream()
.map(Order::getOrderNo)
.collect(Collectors.joining(","));
九、Stream 高频坑点(生产必避)
1. Stream 不可复用
流执行终止操作后即销毁,重复调用直接抛异常。
// 错误示例
Stream<String> stream = names.stream();
stream.count();
stream.collect(Collectors.toList()); // 抛出 IllegalStateException
2. toMap 主键重复报错
未指定合并策略时,key 重复直接抛异常,生产必须加合并策略。
3. 并行流修改共享变量线程不安全
禁止在并行流 forEach 中操作普通集合,统一使用 collect 收集结果。
4. Optional 直接 get 空指针风险
findFirst/max/min 返回 Optional,禁止直接 get(),优先用 ifPresent、orElse。
// 安全写法
users.stream()
.filter(u -> u.getAge() > 100)
.findFirst()
.ifPresent(System.out::println);
十、总结:Stream 核心使用原则
必掌握高频操作
筛选filter、转换map、摊平flatMap、排序sorted、收集collect、分组groupingBy、归并reduce、匹配anyMatch、查询findFirst
核心原则
- 中间操作仅定义流程,终止操作才触发执行
- Stream 只能消费一次,不可复用
- Lambda 尽量无副作用,不修改外部变量
- 并行流谨慎使用,拒绝盲目炫技
- 代码优先清晰可读,不强行堆砌 Stream
Stream 的核心价值是语义化、简洁化、规范化,熟练使用可以彻底告别冗余 for 循环,让 Java 集合代码更优雅、更易维护!
⭐️推荐:
更多推荐


所有评论(0)