⭐ 推荐:社区规则条款 V1.0

如何用 Java Stream 写出更简洁的集合处理代码?

周末
周末 正式会员超兽战士
发布于 2026-10-10 19:33 ·3 浏览 ·0 回复

学会这篇,你就能把「for 循环 + if 判断 + 临时 List」的集合代码,改成一行链式调用,并用 Collectors 一次拿到分组、求和、拼接的结果,代码量通常能砍掉一半。

本文示例基于 JDK 17,IDE 用 IntelliJ IDEA 2023.3。示例数据类用 record 定义(JDK 8~15 请换回普通 class + getter),后面所有代码都基于它:

public record Employee(long id, String name, String dept, double salary, List<String> tags) {}

第一步:用 filter 替代「遍历 + if 收集」

这一步要把手动建 List、循环、判断、add 的四段式,压缩成一次链式调用,结果是直接拿到过滤后的列表。

旧写法:

List<Employee> result = new ArrayList<>();
for (Employee e : employees) {
    if ("研发".equals(e.dept()) && e.salary() > 10000) {
        result.add(e);
    }
}

Stream 写法:

List<Employee> result = employees.stream()
        .filter(e -> "研发".equals(e.dept()))
        .filter(e -> e.salary() > 10000)
        .toList();          // JDK 16+ 才有

JDK 8~15 把最后一行换成 .collect(Collectors.toList())。

注意:Stream.toList() 返回的是不可变列表,对它 add() 会抛 UnsupportedOperationException;Collectors.toList() 返回的是 ArrayList,可以继续修改。另外 "研发".equals(e.dept()) 把常量写在前面,可以避免 dept 为 null 时的 NPE。

第二步:用 map 做字段提取和类型转换

这一步只保留你真正需要的字段,或者把一种类型转成另一种类型,结果是拿到 String、Integer 这类简单值的集合。

List<String> names = employees.stream()
        .map(Employee::name)          // 方法引用,等价于 e -> e.name()
        .distinct()
        .toList();

List<String> upper = names.stream().map(String::toUpperCase).toList();

map 是一对一转换;如果 lambda 体只有一句 getter,IDEA 会提示你用方法引用 Employee::name,小黄灯按 Alt+Enter 可直接转换。

第三步:用 Collectors 一次完成分组、求和、拼接

这一步把原来需要「先分组再遍历累加」的两段逻辑合并,结果是拿到一个 Map 或一个字符串。

// 按部门求工资总额
Map<String, Double> salaryByDept = employees.stream()
        .collect(Collectors.groupingBy(
                Employee::dept,
                Collectors.summingDouble(Employee::salary)));

// 按部门收集姓名列表
Map<String, List<String>> namesByDept = employees.stream()
        .collect(Collectors.groupingBy(
                Employee::dept,
                Collectors.mapping(Employee::name, Collectors.toList())));

// 姓名拼接成一个字符串
String joined = employees.stream()
        .map(Employee::name)
        .collect(Collectors.joining(", ", "【", "】"));

注意:groupingBy 默认返回 HashMap,key 的顺序不保证。如果你要按部门首次出现的顺序输出,需要写成三参数版本 Collectors.groupingBy(Employee::dept, LinkedHashMap::new, Collectors.toList())。

第四步:用 flatMap 拍平嵌套集合

当每个元素里还套着一层集合(比如员工身上的 tags),用 map 会得到 Stream<List<String>>,这一步用 flatMap 把它拍成扁平的 Stream<String>。

List<String> allTags = employees.stream()
        .flatMap(e -> e.tags().stream())
        .distinct()
        .toList();

第五步:短路操作,别再遍历完再判断

这一步把「循环整个集合判断有没有」换成短路写法,结果是元素一命中就立刻返回,后面的元素不再计算。

boolean hasHighPay = employees.stream().anyMatch(e -> e.salary() > 50000);

Optional<Employee> top = employees.stream()
        .max(Comparator.comparingDouble(Employee::salary));

double avg = employees.stream()
        .mapToDouble(Employee::salary)
        .average()
        .orElse(0);

注意:max、findFirst 返回的是 Optional,别直接 .get(),用 orElseThrow(() -> new IllegalStateException("无数据")) 更安全。

调试链式调用时,在 IDEA 里把光标放在 stream() 上,点 Debug 窗口左侧的 Trace Current Stream Chain 按钮,可以逐环节看到元素的进出情况,比在 lambda 里打日志清楚得多。

第六步:判断什么时候不该用 Stream

Stream 不是万能替换。以下三种情况建议保留 for 循环:一是循环里要做 break、continue 加复杂状态累积;二是集合只有几万元素但每秒调用上千次,Stream 的装箱和 lambda 开销会体现出来;三是需要同时操作两个集合的下标。

parallelStream() 只在数据量大(一般 10 万元素以上)、每个元素的计算互不依赖、且没有阻塞 IO 时才考虑。ArrayList 可拆分性好,LinkedList 拆分开销大,用了反而更慢。

小结

  • filter 管筛选,map 管转换,flatMap 管拍平,Collectors 管收口,四类操作覆盖九成场景。
  • Stream.toList() 返回不可变列表,JDK 8~15 用 Collectors.toList()。
  • groupingBy 默认 HashMap 无序,要顺序用 LinkedHashMap::new 三参数版本。
  • 判断「有没有」「取最大」用 anyMatch / max,短路比全遍历快。
  • 循环里有 break、复杂状态或高频小集合调用时,老实用 for。
版权声明:本文来自 GJ站长论坛《如何用 Java Stream 写出更简洁的集合处理代码?》
原文链接:https://www.gj0.com/thread-1520.html
转载请注明出处并保留本声明;内容仅代表作者观点,与本站立场无关。若本文涉嫌侵权,请联系本站处理。
他们都看过 1 人浏览过
GJ论坛站长

全部回复 0

还没有回复,来抢沙发~