基于本文回答
0
评论

在 Java Stream 中,reduce 和 collect 有什么区别?

知识点图片

在 Java Stream API 中,reducecollect 是两个最核心的终端操作(Terminal Operations)。它们都用于将流中的元素组合成一个单一的结果,但它们的设计理念、工作原理和适用场景有本质区别。

简而言之:reduce 侧重于“不可变归约”(Immutable Reduction),而 collect 侧重于“可变归约”(Mutable Reduction)。


1. reduce(不可变归约)

核心概念:

reduce 旨在通过反复应用某个结合操作,将流中的元素合并为一个新的不可变对象
每次操作都会产生一个新值,而不会修改原有的值(类似于 SQL 中的 SUMMINMAX)。

特点:

  • 不可变性:累加器(Accumulator)每次都返回一个新对象(如 StringIntegerBigDecimal 等不可变类)。
  • 返回值:通常返回 Optional<T> 或直接返回计算结果 T

常用场景:

求和、求积、求最大/最小值、连接字符串(小规模)等。

代码示例:

java
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

// 示例 1:求和(有初始值 0)
int sum = numbers.stream()
                 .reduce(0, (a, b) -> a + b); // 15

// 示例 2:求最大值(无初始值,返回 Optional)
Optional<Integer> max = numbers.stream()
                               .reduce(Integer::max); // Optional[5]

2. collect(可变归约)

核心概念:

collect 旨在将流中的元素收集到一个可变的容器中(如 ListSetMapStringBuilder 等)。
它不会每次都创建新容器,而是将元素追加(mutate)到同一个现有的容器中。

特点:

  • 可变性:操作的是可变容器,效率极高(避免了频繁创建新对象的开销)。
  • 容器化:配合 Collectors 工具类可以实现极其复杂的聚合、分组和转换逻辑。

常用场景:

将流转换为 List/Set/Map、分组(groupingBy)、分区(partitioningBy)、字符串拼接(joining)等。

代码示例:

java
List<String> words = Arrays.asList("apple", "banana", "cherry");

// 示例 1:收集到 List 中
List<String> list = words.stream()
                         .filter(w -> w.startsWith("a"))
                         .collect(Collectors.toList()); // [apple]

// 示例 2:分组
Map<Integer, List<String>> groupedByLength = words.stream()
    .collect(Collectors.groupingBy(String::length)); // {5=[apple], 6=[banana, cherry]}

3. 核心区别对比

特性 reduce collect
主要设计目的 得到一个不可变的单一结果(数值、布尔值等) 得到一个可变的容器/集合结果(List、Set、Map等)
累加机制 每次生成一个新对象(例如 a + b 产生新数) 将元素放入已有容器(例如 list.add(element)
并行流性能 适用于轻量级的数据(如基本类型),若用于集合会产生大量对象拷贝,性能极差。 并行流性能极佳。多个线程分别收集到局部容器中,最后通过 combiner 合并容器。
典型返回值 Optional<T>, T List<T>, Set<T>, Map<K, V>, String
核心参数 identity(初始值)
accumulator(累加器)
combiner(组合器,用于并行流)
supplier(创建容器)
accumulator(加入容器)
combiner(合并容器)

4. 为什么不能混用?(避坑指南)

错误示范:用 reduce 来收集 List

有人可能会写出这样的代码,尝试用 reduce 把元素装进 ArrayList

java
// !!! 极其糟糕的写法 !!!
List<Integer> list = stream.reduce(
    new ArrayList<Integer>(), 
    (l, e) -> { l.add(e); return l; }, // 违反了 reduce 的不可变契约
    (l1, l2) -> { l1.addAll(l2); return l1; }
);

为什么糟糕?

  1. 线程安全问题:在并行流中,多个线程会并发修改同一个 ArrayList,导致数据竞争和未定义行为(reduce 默认传入的初始值应该是个“恒等值”,每次结合不应修改原对象)。
  2. 违背设计直觉:这本质上是可变操作,应该使用 collect
    java
    List<Integer> list = stream.collect(ArrayList::new, List::add, List::addAll);
    // 或者更简单的:
    List<Integer> list = stream.collect(Collectors.toList());

总结建议

  • 当你需要计算一个数值(求和、最大值、布尔判断等),或者处理的是基础数据类型/不可变对象时,优先选择 reduce
  • 当你需要整理数据结构(转为 List/Set/Map、拼接字符串、按属性分组等),需要将结果放入一个可变容器时,必须选择 collect
右滑查看面试常问