JDK-理解Stream API-Collector收集器
Collector<T, A, R> 是 Java Stream API 中用于自定义收集操作的核心接口。它允许开发者定义如何将流中的元素累积到一个可变容器中,并最终将容器转换为结果。Collector 的设计非常灵活,能够支持各种复杂的收集操作,例如分组、分区、连接字符串等。
接下来,我们将从 设计理念 和 底层原理 两个方面深入解析 Collector。
一、设计理念
Collector 的设计目标是解耦流的处理逻辑与结果生成逻辑,同时提供足够的灵活性来支持各种收集操作。它的设计理念可以概括为以下几点:
可变容器(Accumulator):
收集操作需要一个可变容器来临时存储流中的元素。
容器类型可以是任意的(如
List、Map、StringBuilder等)。
累积操作(Accumulation):
定义如何将流中的元素逐个添加到可变容器中。
结果转换(Finisher):
在流处理完成后,将可变容器转换为最终结果(如将
List转换为不可变集合)。
并行支持(Combiner):
在并行流中,多个线程会独立处理数据块,最终需要将这些部分结果合并。
特性描述(Characteristics):
描述收集器的行为特性(如是否支持并行、是否有序等)。
二、Collector 接口定义
Collector 接口的定义如下:
public interface Collector<T, A, R> {
// 创建一个新的可变容器
Supplier<A> supplier();
// 将元素累积到容器中
BiConsumer<A, T> accumulator();
// 合并两个容器(用于并行流)
BinaryOperator<A> combiner();
// 将容器转换为最终结果
Function<A, R> finisher();
// 描述收集器的特性
Set<Characteristics> characteristics();
}
1. 核心方法解析
2. 特性枚举(Characteristics)
Characteristics 是一个枚举类型,用于描述收集器的行为特性:
三、底层原理
Collector 的底层实现依赖于 Stream API 的流水线机制。以下是其工作原理的详细解析:
1. 收集器的执行流程
当调用 collect(Collector) 方法时,Stream API 会按照以下步骤执行收集操作:
创建容器:
调用
supplier()方法创建一个新的可变容器(如ArrayList)。
累积元素:
对流中的每个元素调用
accumulator()方法,将其添加到容器中。
合并部分结果(并行流):
在并行流中,多个线程会独立处理数据块,生成多个部分结果。
调用
combiner()方法将这些部分结果合并。
转换结果:
调用
finisher()方法将容器转换为最终结果(如将List转换为Set)。
2. 并行流的支持
在并行流中,Collector 的执行流程如下:
分割数据:
使用
Spliterator将数据源分割为多个子任务。
并行累积:
每个子任务独立调用
supplier()创建自己的容器,并使用accumulator()累积元素。
合并结果:
使用
combiner()方法将各个子任务的结果合并。
3. 特性优化
Collector 的特性(Characteristics)会影响其执行方式:
CONCURRENT:如果收集器是并发的,多个线程可以共享同一个容器,无需合并部分结果。
例如,
Collectors.toConcurrentMap()是一个并发收集器。
IDENTITY_FINISH:如果
finisher()是恒等函数,Stream API 会跳过此步骤,直接返回容器。
四、自定义 Collector 示例
为了更好地理解 Collector 的工作原理,我们实现一个自定义收集器:将流中的字符串连接成一个用逗号分隔的字符串。
1. 自定义收集器实现
public class StringJoiningCollector implements Collector<String, StringBuilder, String> {
private final String delimiter;
public StringJoiningCollector(String delimiter) {
this.delimiter = delimiter;
}
@Override
public Supplier<StringBuilder> supplier() {
return StringBuilder::new; // 创建容器
}
@Override
public BiConsumer<StringBuilder, String> accumulator() {
return (sb, str) -> {
if (sb.length() > 0) {
sb.append(delimiter); // 添加分隔符
}
sb.append(str); // 添加元素
};
}
@Override
public BinaryOperator<StringBuilder> combiner() {
return (sb1, sb2) -> {
if (sb1.length() > 0 && sb2.length() > 0) {
sb1.append(delimiter); // 合并时添加分隔符
}
sb1.append(sb2); // 合并两个容器
return sb1;
};
}
@Override
public Function<StringBuilder, String> finisher() {
return StringBuilder::toString; // 转换为最终结果
}
@Override
public Set<Characteristics> characteristics() {
return Set.of(); // 无特殊特性
}
}
2. 使用自定义收集器
List<String> words = Arrays.asList("Hello", "World", "Java", "Stream");
String result = words.stream().collect(new StringJoiningCollector(", "));
System.out.println(result); // 输出: Hello, World, Java, Stream
五、JDK 内置收集器
JDK 提供了许多内置的收集器(位于 java.util.stream.Collectors 类中),例如:
toList():将元素收集到List中。toSet():将元素收集到Set中。joining():将字符串连接成一个字符串。groupingBy():按条件分组。partitioningBy():按条件分区。
这些内置收集器都是基于 Collector 接口实现的。
六、总结
Collector<T, A, R> 的设计理念是通过解耦累积逻辑与结果生成逻辑,提供一种灵活且高效的方式来处理流中的元素。其底层原理基于以下核心机制:
可变容器:用于临时存储元素。
累积操作:将元素逐个添加到容器中。
结果转换:将容器转换为最终结果。
并行支持:通过
combiner()方法合并部分结果。
通过理解 Collector 的设计和实现,开发者可以更好地使用内置收集器,甚至实现自定义的收集操作以满足特定需求。