Java Stream 的 collect 与 reduce 操作示例详解

 更新时间:2025年09月06日 09:50:43   作者:潜意识Java  
在 Java Stream API 中,collect 和 reduce 是两种强大的终止操作,用于将流中的元素累积为最终结果,本文将从核心概念、使用场景、性能特性等多个维度进行对比分析,感兴趣的朋友跟随小编一起看看吧

在 Java Stream API 中,collect 和 reduce 是两种强大的终止操作,用于将流中的元素累积为最终结果。尽管它们都用于聚合数据,但实现机制和应用场景存在显著差异。本文将从核心概念、使用场景、性能特性等多个维度进行对比分析。

一、核心概念对比

特性collect(Collector)reduce(BinaryOperator)
操作类型可变容器汇聚操作不可变值累积操作
结果类型可变容器(如 List、Set、Map)单个值或容器(需通过 Supplier 创建)
数据结构支持中途修改的集合不可变对象的累积
并行处理效率高(可并发操作独立容器后合并)低(需按顺序累积或复杂 combiner)
典型应用场景数据分组、分区、转换集合类型求和、求最大值、字符串拼接

二、collect 方法详解

collect 是一种可变容器汇聚操作,通过 Collector 接口实现复杂的归约逻辑。它将流中的元素累积到一个可变容器(如 List、Set、Map)中,并支持进一步的处理。

1. 基础用法示例

// 将 Stream 转换为 List
List<String> names = Stream.of("Alice", "Bob", "Charlie")
    .collect(Collectors.toList());
// 将 Stream 转换为 Set 去重
Set<Integer> uniqueNumbers = Stream.of(1, 2, 2, 3, 3, 3)
    .collect(Collectors.toSet());
// 使用 toCollection 指定具体集合类型
LinkedList<String> linkedList = Stream.of("a", "b", "c")
    .collect(Collectors.toCollection(LinkedList::new));

2. 高级用法:分组与分区

class Person {
    private String name;
    private int age;
    private String city;
    // 构造器、getter 略
}
// 按城市分组
Map<String, List<Person>> peopleByCity = personStream
    .collect(Collectors.groupingBy(Person::getCity));
// 按年龄是否大于18分区
Map<Boolean, List<Person>> partitionByAge = personStream
    .collect(Collectors.partitioningBy(p -> p.getAge() > 18));

3. 自定义 Collector

// 自定义 Collector 实现字符串拼接
Collector<String, StringBuilder, String> stringCollector = Collector.of(
    StringBuilder::new,                  // 创建容器
    StringBuilder::append,               // 累积元素
    (sb1, sb2) -> sb1.append(sb2),       // 合并容器
    StringBuilder::toString              // 最终转换
);
String result = Stream.of("Hello", " ", "World")
    .collect(stringCollector);  // 输出:Hello World

三、reduce 方法详解

reduce 是一种不可变值累积操作,通过二元操作(BinaryOperator)将流中的元素依次处理,最终得到一个值。

1. 基础用法示例

// 无初始值的 reduce(返回 Optional)
Optional<Integer> sum = Stream.of(1, 2, 3, 4)
    .reduce(Integer::sum);  // 结果:10
// 有初始值的 reduce
int product = Stream.of(1, 2, 3, 4)
    .reduce(1, (a, b) -> a * b);  // 结果:24
// 复杂累积:字符串拼接
String concatenated = Stream.of("a", "b", "c")
    .reduce("", String::concat);  // 结果:abc

2. 并行流中的 reduce

// 并行流中使用带 combiner 的 reduce
int sum = Stream.of(1, 2, 3, 4)
    .parallel()
    .reduce(0,  // 初始值
        Integer::sum,  // 累积器
        Integer::sum); // 合并器(并行流必须)

3. 自定义累积逻辑

// 计算最大值
Optional<Integer> max = Stream.of(5, 3, 9, 1)
    .reduce((a, b) -> a > b ? a : b);  // 结果:9
// 计算平均值(需要自定义累积器)
class Average {
    private int sum;
    private int count;
    public double get() {
        return count > 0 ? (double) sum / count : 0;
    }
}
Average average = Stream.of(1, 2, 3, 4, 5)
    .reduce(new Average(),
        (avg, num) -> {
            avg.sum += num;
            avg.count++;
            return avg;
        },
        (avg1, avg2) -> {
            avg1.sum += avg2.sum;
            avg1.count += avg2.count;
            return avg1;
        });
double result = average.get();  // 结果:3.0

四、核心区别与选择策略

场景collect 更适合reduce 更适合
结果类型集合或复杂对象单个值(如数字、字符串)
并行处理效率高(独立容器可并发合并)低(需顺序累积或复杂 combiner)
累积过程是否可变可变(操作集合内部元素)不可变(生成新对象)
是否需要分组 / 分区是(groupingBy、partitioningBy)
是否需要多阶段处理是(Collector 链)

五、性能对比与优化建议

  • 并行流场景
    • collect:由于支持并发操作独立容器后合并,性能通常优于 reduce
    • reduce:若未正确实现 combiner,可能导致并行效率低下
  • 大集合处理
    • collect:在分组、分区等复杂操作中表现更优
    • reduce:在简单累积(如求和)中性能相当,但代码可能更简洁
  • 内存占用
    • collect:需要创建中间容器,内存占用较高
    • reduce:仅维护累积值,内存占用较低

六、实战案例对比

1. 统计员工平均年龄

// 使用 collect
double averageAge = employees.stream()
    .collect(Collectors.averagingInt(Employee::getAge));
// 使用 reduce
OptionalDouble averageAge = employees.stream()
    .mapToInt(Employee::getAge)
    .average();

2. 按部门分组员工

// 使用 collect(推荐)
Map<String, List<Employee>> employeesByDepartment = employees.stream()
    .collect(Collectors.groupingBy(Employee::getDepartment));
// 使用 reduce(复杂且低效)
Map<String, List<Employee>> result = employees.stream()
    .reduce(
        new HashMap<>(),
        (map, emp) -> {
            map.computeIfAbsent(emp.getDepartment(), k -> new ArrayList<>())
               .add(emp);
            return map;
        },
        (m1, m2) -> {
            m2.forEach((dept, emps) -> 
                m1.computeIfAbsent(dept, k -> new ArrayList<>())
                  .addAll(emps));
            return m1;
        }
    );

3. 字符串拼接

// 使用 collect
String result = words.stream()
    .collect(Collectors.joining(", "));
// 使用 reduce
String result = words.stream()
    .reduce("", (s1, s2) -> s1.isEmpty() ? s2 : s1 + ", " + s2);

七、总结与最佳实践

  • 优先使用 collect
    • 当需要生成集合、分组数据或执行复杂转换时
    • 在并行流场景中,collect 的性能通常更优
  • 使用 reduce
    • 当需要计算单个值(如求和、最大值)时
    • 当累积过程不需要中间容器,且可以通过二元操作表达时
  • 复杂场景组合使用
// 先分组,再计算每组的平均值
Map<String, Double> avgScoreByClass = students.stream()
    .collect(Collectors.groupingBy(
        Student::getClassName,
        Collectors.averagingDouble(Student::getScore)
    ));
  • 避免过度使用 reduce
    • 对于复杂的集合操作,使用 reduce 可能导致代码冗长且难以维护,应优先考虑 collect
    • 通过合理选择 collect 和 reduce,可以使代码更加简洁、高效,同时充分发挥 Stream API 的优势。

到此这篇关于Java Stream 的 collect 与 reduce 操作示例详解的文章就介绍到这了,更多相关Java Stream 的 collect 与 reduce内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 利用Java制作字符动画实例代码

    利用Java制作字符动画实例代码

    这篇文章主要给大家介绍了关于如何利用Java制作字符动画的相关资料,文中通过示例代码介绍的非常详细,对大家学习或者使用Java具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧
    2019-05-05
  • Java中对象的创建和销毁过程详析

    Java中对象的创建和销毁过程详析

    这篇文章主要介绍了Java中对象的创建和销毁过程,对象的创建过程包括类加载检查、内存分配、初始化零值内存、设置对象头和执行init方法,对象的销毁过程由垃圾回收机制负责,文中介绍的非常详细,需要的朋友可以参考下
    2025-02-02
  • 解决IDEA光标变成白色粗条的问题

    解决IDEA光标变成白色粗条的问题

    这篇文章主要介绍了解决IDEA光标变成白色粗条的问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2021-02-02
  • MyBatis源码解析——获取SqlSessionFactory方式

    MyBatis源码解析——获取SqlSessionFactory方式

    这篇文章主要介绍了MyBatis源码解析——获取SqlSessionFactory方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-12-12
  • 关于SpringBoot启动速度慢的原因总结

    关于SpringBoot启动速度慢的原因总结

    这篇文章主要介绍了关于SpringBoot启动速度慢的原因总结,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-05-05
  • Java synchronized关键字和Lock接口实现原理

    Java synchronized关键字和Lock接口实现原理

    这篇文章主要介绍了Java synchronized关键字和Lock接口实现原理,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-12-12
  • Spring Cloud Eureka服务注册中心入门流程分析

    Spring Cloud Eureka服务注册中心入门流程分析

    这篇文章主要介绍了Spring Cloud Eureka服务注册中心入门流程分析,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2022-06-06
  • Java多线程之ThreadLocal原理总结

    Java多线程之ThreadLocal原理总结

    这篇文章主要介绍了Java多线程ThreadLocal原理,同一个ThreadLocal所包含的对象,在不同的Thread中有不同的副本,文章中有详细的代码示例,需要的朋友参考一下
    2023-04-04
  • Scala入门之List使用详解

    Scala入门之List使用详解

    这篇文章主要介绍了Scala入门之List使用详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-06-06
  • MyBatis编写嵌套子查询的动态SQL实践详解

    MyBatis编写嵌套子查询的动态SQL实践详解

    在Java生态中,MyBatis作为一款优秀的ORM框架,广泛应用于数据库操作,本文将深入探讨如何在MyBatis中编写嵌套子查询的动态SQL,并结合实际案例分析其应用场景与实现技巧
    2025-06-06

最新评论