Java案例怎么用Stream遍历集合?

wen java案例 13

精通Java Stream:如何优雅地遍历集合?7大实战案例详解

目录导读

  • 为什么需要Stream遍历集合?
  • Stream与传统遍历对比:谁更胜一筹?
  • 7大实战案例:从基础到高阶
    • 案例1:基础集合遍历(forEach)
    • 案例2:条件过滤(filter)
    • 案例3:数据转换(map)
    • 案例4:去重与排序(distinct/sorted)
    • 案例5:聚合统计(reduce/count)
    • 案例6:并行流遍历(parallelStream)
    • 案例7:自定义对象复杂遍历
  • 常见问题与避坑指南
  • Q&A:你问我答

为什么需要Stream遍历集合?

在Java 8之前,遍历集合无非就是for循环、Iterator迭代器,但面对复杂业务——比如既要过滤、又要转换、还要统计——代码会变得冗长且难以维护,Stream API正是为此而生:它让集合操作变得像SQL查询一样声明式、链式、高效

Java案例怎么用Stream遍历集合?

根据Oracle官方文档和Stack Overflow的统计,合理使用Stream能使业务代码减少40%以上,且更利于并行处理。


Stream与传统遍历对比:谁更胜一筹?

场景 传统for循环 Stream
过滤+打印 4行+if判断 1行链式调用
多条件转换 需临时变量 函数式映射
大数据量 单线程,效率低 支持并行流
代码可读性 命令式,逻辑分散 声明式,意图明确

Stream不是替代品,而是更优雅的解决方案,尤其适合数据筛选、转换、聚合场景。


7大实战案例

以下案例均基于JDK 8+,集合类型为List和自定义对象。

案例1:基础集合遍历(forEach)

List<String> names = Arrays.asList("Tom", "Jerry", "Alice");
// 传统方式
for (String name : names) { System.out.println(name); }
// Stream方式
names.stream().forEach(System.out::println);

图解stream()创建流,forEach是终端操作,依次消费每个元素,不需要显式写循环变量,代码量减半。

案例2:条件过滤(filter)

需求:获取长度大于3的名字。

names.stream()
    .filter(name -> name.length() > 3)
    .collect(Collectors.toList());
// 结果:[Jerry, Alice]

filter接收一个Predicate(布尔返回值的函数),只有满足条件的元素才会进入下一环节,这是Stream最常用的“守门员”操作。

案例3:数据转换(map)

需求:将所有名字转为大写。

names.stream()
    .map(String::toUpperCase)
    .forEach(System.out::println);
// TOM JERRY ALICE

map是一对一转换,将原始元素映射成新元素,可以多次链式调用,例如先过滤再转换。

案例4:去重与排序(distinct/sorted)

List<Integer> numbers = Arrays.asList(3, 1, 4, 1, 5, 9, 2, 6, 5);
numbers.stream()
    .distinct()       // 去重:[3,1,4,5,9,2,6]
    .sorted()         // 升序排序:[1,2,3,4,5,6,9]
    .collect(Collectors.toList());

注意:sorted可传入自定义Comparator,对于自定义对象,需要实现Comparable接口,或者用Lambda指定排序规则。

案例5:聚合统计(reduce/count)

统计所有数字之和:

int sum = numbers.stream().reduce(0, Integer::sum); // 31
long count = numbers.stream().filter(n -> n > 3).count(); // 4

reduce是归约操作,将流中的元素反复结合得到单个值。count是常见聚合函数。

案例6:并行流遍历(parallelStream)

当集合数据量巨大(如10万条以上),可启用并行流:

List<Transaction> bigList = ...; // 假设10万条
bigList.parallelStream()
    .filter(t -> t.getAmount() > 1000)
    .forEach(t -> process(t));

注意:并行流利用Fork/Join框架,自动拆分数据到多线程处理,但线程安全需自行保证,且不适合有状态的操作(如累加器)。

案例7:自定义对象复杂遍历

假设有Person类(name, age, city),需求:获取年龄>25且来自北京的人名,并按年龄降序。

List<Person> people = ...;
people.stream()
    .filter(p -> p.getAge() > 25 && "北京".equals(p.getCity()))
    .sorted(Comparator.comparingInt(Person::getAge).reversed())
    .map(Person::getName)
    .collect(Collectors.toList());

一行代码完成了过滤、排序、提取、收集,而传统方式至少需要5-8行。


常见问题与避坑指南

  1. Stream只能使用一次:一个Stream执行了终端操作后就关闭了,不能重复使用。
  2. 惰性求值:中间操作(如filter、map)不会立即执行,只有遇到终端操作(如collect、forEach)才触发。
  3. 并行流并非万能:数据量小、有顺序要求、存在共享变量时不宜用并行流。
  4. 空指针:集合本身为null会直接抛NPE,一定要先判空。

Q&A:你问我答

Q1:Stream遍历和for循环哪个性能更好?

A:对于小数据量(几千条以内),for循环略快(Stream有对象创建开销),但对于大数据量,尤其是并行流场景,Stream性能明显占优,且Stream代码更简洁,可读性更高。

Q2:Stream可以处理Map吗?

A:可以!map.entrySet().stream()再将Entry转为Stream,然后对key或value进行各种操作。

Q3:Stream中如何将结果转为数组?

A:使用toArray()方法:names.stream().toArray(String[]::new)

Q4:是否可以嵌套遍历?

A:可以,但建议用flatMap展平流,避免嵌套循环,例如listOfLists.stream().flatMap(List::stream)

Q5:Stream是否支持跳过和限制?

A:支持!skip(n)跳过前n个,limit(n)限制只取前n个,常用于分页场景。

Q6:遇到“stream has already been operated upon or closed”怎么解决?

A:每次操作后重建Stream,或使用Supplier来供应新的流。Supplier<Stream<String>> streamSupplier = () -> names.stream();


Stream是Java函数式编程的核心工具,通过7个实操案例,你已经掌握了从基础遍历到复杂自定义对象处理的全部技巧,先创建流,中间操作链式编排,最后终端操作输出结果,多加练习,你的代码会越来越“Stream化”!

延伸阅读:如果你想深入Stream底层原理,推荐阅读《Java 8实战》或查阅Oracle官方API文档。

抱歉,评论功能暂时关闭!