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内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • SpringBoot如何使用@Cacheable进行缓存与取值

    SpringBoot如何使用@Cacheable进行缓存与取值

    这篇文章主要介绍了SpringBoot如何使用@Cacheable进行缓存与取值,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-08-08
  • 解决mybatis case when 报错的问题

    解决mybatis case when 报错的问题

    这篇文章主要介绍了解决mybatis case when 报错的问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2021-02-02
  • mybatis中使用InsertProvider注解报错解决全过程

    mybatis中使用InsertProvider注解报错解决全过程

    这篇文章主要介绍了mybatis中使用InsertProvider注解报错解决全过程,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-07-07
  • PropertiesLoaderUtils 出现中文乱码的解决方式

    PropertiesLoaderUtils 出现中文乱码的解决方式

    这篇文章主要介绍了PropertiesLoaderUtils 出现中文乱码的解决方式,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-08-08
  • SpringBoot @JsonDeserialize自定义Json序列化方式

    SpringBoot @JsonDeserialize自定义Json序列化方式

    这篇文章主要介绍了SpringBoot @JsonDeserialize自定义Json序列化方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-10-10
  • java有序二叉树的删除节点方式

    java有序二叉树的删除节点方式

    文章描述了在二叉树中删除节点的三种情况及其对应的操作步骤,通过递归找到节点及其父节点,并根据节点的子树情况(无子树、单子树、双子树)进行相应的删除操作,文章还提供了一个测试类来验证删除操作的正确性
    2024-12-12
  • Java使用二分法进行查找和排序的示例

    Java使用二分法进行查找和排序的示例

    这篇文章主要介绍了Java使用二分法进行查找和排序的示例,二分插入排序和二分查找是基础的算法,需要的朋友可以参考下
    2016-04-04
  • 解决mybatis resultMap根据type找不到对应的包问题

    解决mybatis resultMap根据type找不到对应的包问题

    这篇文章主要介绍了解决mybatis resultMap根据type找不到对应的包问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-08-08
  • idea代码模板设置方式

    idea代码模板设置方式

    这篇文章主要介绍了idea代码模板设置方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2023-12-12
  • Java中关于String的全面解析

    Java中关于String的全面解析

    这篇文章主要介绍了Java中关于String全面解析,下面我们来一起学习一下吧
    2019-05-05

最新评论