Java 流式编程(Stream)完整详解
目录
一、基础概念
1. 流的三要素
2. 流的特点
二、创建 Stream(数据源)
1. 从集合创建(最常用)
2. 从数组创建
3. 直接创建空流 / 指定元素流
4. 无限流(生成 / 迭代)
三、中间操作(Intermediate)
1. 常用无状态操作(不依赖前后元素)
filter 过滤
map 映射(类型转换 / 字段提取)
flatMap 扁平化映射(拆分层级)
peek 查看 / 调试流
2. 常用有状态操作(依赖全部元素 / 顺序)
distinct 去重
sorted 排序
limit /skip 截取 / 跳过
四、终端操作(Terminal)
1. forEach /forEachOrdered 遍历
2. collect 收集(最核心)
① 转为 List / Set / 数组
② 拼接字符串 joining
③ 分组 groupingBy(类似 SQL group by)
④ 分区 partitioningBy(二分区:true/false)
⑤ 聚合统计(count、max、min、avg、sum)
3. 匹配操作(返回 boolean)
4. 查找操作
5. 规约 reduce(聚合计算,自定义累加)
五、数值流(IntStream / LongStream / DoubleStream)
1. 转换数值流
2. 范围创建数值流
3. 数值便捷计算
六、并行流 parallelStream
1. 开启方式
2. 原理
3. 注意事项(坑)
七、完整实战示例(传统循环 VS 流式)
传统 for 循环
Stream 流式写法(一行链式)
八、常见避坑总结
九、Java 9+ 新增增强(拓展)
Java 8 引入Stream 流,用于集合 / 数组的链式、函数式数据处理,替代传统循环遍历,代码更简洁、可读性强,支持并行计算。
核心定位:不是数据结构,是数据的「处理管道」,不存储数据,只操作数据源。
一、基础概念
1. 流的三要素
- 数据源:集合、数组、IO 流、生成器等
- 中间操作:链式调用,延迟执行(返回新 Stream),可多次叠加
- 终端操作:触发整个流执行,关闭流(流只能用一次)
2. 流的特点
- 惰性求值:中间操作直到终端调用才执行
- 单向不可逆:流一旦终端消费,不能重复使用
- 不修改原数据源:所有操作返回新结果,原集合不变
- 支持并行流:简单切换即可利用多线程
二、创建 Stream(数据源)
1. 从集合创建(最常用)
List<String> list = Arrays.asList("A", "B", "C"); // 顺序流 Stream<String> stream = list.stream(); // 并行流 Stream<String> parallelStream = list.parallelStream();2. 从数组创建
String[] arr = {"1", "2", "3"}; Stream<String> stream = Arrays.stream(arr);3. 直接创建空流 / 指定元素流
// 空流 Stream<String> empty = Stream.empty(); // 可变参数创建流 Stream<Integer> numStream = Stream.of(1, 2, 3, 4);4. 无限流(生成 / 迭代)
常用于造测试数据,必须配合 limit 截断,否则死循环
// 迭代:从0开始,每次+2 Stream.iterate(0, n -> n + 2).limit(5).forEach(System.out::println); // 生成:随机数 Stream.generate(Math::random).limit(3).forEach(System.out::println);三、中间操作(Intermediate)
返回 Stream,可链式拼接,延迟执行,分为:无状态、有状态。
1. 常用无状态操作(不依赖前后元素)
filter 过滤
筛选符合条件元素,参数Predicate断言
List<Integer> nums = Arrays.asList(1,2,3,4,5,6); nums.stream() .filter(n -> n > 3) // 保留大于3的数 .forEach(System.out::println);map 映射(类型转换 / 字段提取)
一对一转换,参数Function
// 数字转字符串 nums.stream().map(String::valueOf).forEach(System.out::println); // 实体类提取字段(示例) class User { String name; } List<User> userList = new ArrayList<>(); userList.stream().map(User::getName).forEach(System.out::println);flatMap 扁平化映射(拆分层级)
一对多,把流中的集合 / 数组拆解为单个元素,解决嵌套集合
List<List<String>> nested = Arrays.asList( Arrays.asList("a","b"), Arrays.asList("c","d") ); // 扁平化:两层集合 → 单层流 nested.stream() .flatMap(Collection::stream) .forEach(System.out::println);peek 查看 / 调试流
遍历元素,不改变流,多用于打印日志、调试
nums.stream() .peek(n -> System.out.println("原始:" + n)) .filter(n -> n % 2 == 0) .peek(n -> System.out.println("过滤后:" + n)) .count(); // 终端触发执行2. 常用有状态操作(依赖全部元素 / 顺序)
distinct 去重
基于equals()+hashCode()去重
Arrays.asList(1,2,2,3,3,3).stream() .distinct() .forEach(System.out::println);sorted 排序
- 无参:自然排序(实现 Comparable)
- 有参:自定义 Comparator 比较器
// 自然升序 nums.stream().sorted().forEach(System.out::println); // 自定义降序 nums.stream().sorted(Comparator.reverseOrder()).forEach(System.out::println);limit /skip 截取 / 跳过
limit(n):取前 n 个元素skip(n):跳过前 n 个元素
// 跳过前2个,取后3个 nums.stream().skip(2).limit(3).forEach(System.out::println);四、终端操作(Terminal)
触发流执行,流关闭,不能继续链式调用,分为:遍历、收集、匹配、聚合、规约等。
1. forEach /forEachOrdered 遍历
forEach:顺序流有序,并行流无序forEachOrdered:并行流也保证遍历顺序
nums.stream().forEach(System.out::println); nums.parallelStream().forEachOrdered(System.out::println);2. collect 收集(最核心)
将流转回 集合、字符串、分组、分区等,Collectors工具类提供大量静态方法。
① 转为 List / Set / 数组
// 转 List List<Integer> list = nums.stream().collect(Collectors.toList()); // 转 Set(自动去重) Set<Integer> set = nums.stream().collect(Collectors.toSet()); // 转数组 Integer[] array = nums.stream().toArray(Integer[]::new);② 拼接字符串 joining
List<String> strs = Arrays.asList("Java","Stream","Demo"); // 直接拼接 String s1 = strs.stream().collect(Collectors.joining()); // 分隔符拼接 String s2 = strs.stream().collect(Collectors.joining(",")); // 分隔符 + 前缀 + 后缀 String s3 = strs.stream().collect(Collectors.joining(",", "[", "]"));③ 分组 groupingBy(类似 SQL group by)
List<User> userList = Arrays.asList( new User("张三", 20), new User("李四", 22), new User("王五", 20) ); // 按年龄分组 key:年龄 value:同年龄用户集合 Map<Integer, List<User>> group = userList.stream() .collect(Collectors.groupingBy(User::getAge));④ 分区 partitioningBy(二分区:true/false)
只能分成两组,返回Map<Boolean, List<T>>
// 分区:年龄>=20 / <20 Map<Boolean, List<User>> part = userList.stream() .collect(Collectors.partitioningBy(u -> u.getAge() >= 20));⑤ 聚合统计(count、max、min、avg、sum)
// 一次性统计:数量、总和、最大、最小、平均值 IntSummaryStatistics stat = nums.stream() .collect(Collectors.summarizingInt(Integer::intValue)); stat.getCount(); stat.getSum(); stat.getMax();3. 匹配操作(返回 boolean)
// allMatch:全部满足 boolean all = nums.stream().allMatch(n -> n > 0); // anyMatch:任意一个满足 boolean any = nums.stream().anyMatch(n -> n == 3); // noneMatch:全部不满足 boolean none = nums.stream().noneMatch(n -> n < 0);4. 查找操作
// findFirst:获取第一个元素(顺序流稳定) Optional<Integer> first = nums.stream().findFirst(); // findAny:获取任意元素(并行流效率更高) Optional<Integer> anyEle = nums.parallelStream().findAny();注意:返回
Optional,防止空指针。
5. 规约 reduce(聚合计算,自定义累加)
把流中元素反复结合得到一个值,常用于求和、求积、拼接。
List<Integer> numList = Arrays.asList(1,2,3,4); // 1. 无初始值:返回 Optional Optional<Integer> sum1 = numList.stream().reduce(Integer::sum); // 2. 有初始值:直接返回结果(不会空) Integer sum2 = numList.stream().reduce(0, Integer::sum); // 自定义累加逻辑 Integer total = numList.stream().reduce(0, (a, b) -> a + b * 2);五、数值流(IntStream / LongStream / DoubleStream)
普通Stream<Integer>存在自动装箱 / 拆箱损耗,Java 提供专用数值流提升性能。
1. 转换数值流
List<Integer> list = Arrays.asList(1,2,3); // 转 IntStream IntStream intStream = list.stream().mapToInt(Integer::intValue); // 转回包装流 Stream<Integer> stream = intStream.boxed();2. 范围创建数值流
// [1,5] 闭区间 IntStream.rangeClosed(1,5).forEach(System.out::println); // [1,5) 左闭右开 IntStream.range(1,5).forEach(System.out::println);3. 数值便捷计算
int sum = intStream.sum(); int max = intStream.max().getAsInt(); double avg = intStream.average().getAsDouble();六、并行流 parallelStream
1. 开启方式
// 方式1:集合直接调用 list.parallelStream(); // 方式2:顺序流转并行 list.stream().parallel();2. 原理
底层基于Fork/Join 框架,多线程拆分任务并行执行,大数据集提升明显。
3. 注意事项(坑)
- 非线程安全集合不要用并行流,会并发修改异常
- 依赖顺序的逻辑慎用并行流(
findAny、forEach会乱序) - 小数据量:并行流有线程开销,反而更慢
- 不要在并行流中操作外部可变变量
七、完整实战示例(传统循环 VS 流式)
需求:过滤偶数 → 平方 → 去重 → 收集为 List
传统 for 循环
List<Integer> source = Arrays.asList(1,2,2,3,4,4,5); List<Integer> result = new ArrayList<>(); for (Integer n : source) { if (n % 2 == 0) { int square = n * n; if (!result.contains(square)) { result.add(square); } } }Stream 流式写法(一行链式)
List<Integer> result = source.stream() .filter(n -> n % 2 == 0) .map(n -> n * n) .distinct() .collect(Collectors.toList());八、常见避坑总结
- 流只能使用一次:终端操作后流关闭,重复调用抛
IllegalStateException - 中间操作延迟执行:没有终端操作,中间代码完全不运行
- 避免在流中修改原集合 / 外部变量
- 实体类去重、比较务必重写
equals()和hashCode() - 基础类型优先用数值流,减少装箱拆箱
- 并行流只适合无状态、不依赖顺序、大数据量场景
九、Java 9+ 新增增强(拓展)
Stream.ofNullable():支持单个 null 元素创建流takeWhile() / dropWhile():按条件截断流- 集合新增
stream()重载,优化空集合场景
