Loading

Java接口Collector详解

Collector<T, A, R> 是 Java Stream API 的核心接口之一,用于定义可变的归约操作(如 collect() 方法中的操作)。它的核心作用是将流中的元素累积到可变容器中,并可选择对结果进行最终转换。

原理简介

接口定义:

public interface Collector<T, A, R> {
    Supplier<A> supplier();
    BiConsumer<A, T> accumulator();
    BinaryOperator<A> combiner();
    Function<A, R> finisher();
    Set<Characteristics> characteristics();
}

泛型参数含义:

  • T:流中元素的类型(输入类型)。
  • A:累加器的类型(中间可变容器的类型,如 ArrayList)。
  • R:最终结果的类型(如 List<T>String)。

必须实现的4个核心方法:

  • Supplier<A> supplier(): 返回一个空的累加器实例(如 ArrayList::new)。
  • BiConsumer<A, T> accumulator(): 定义如何将元素合并到累加器(如 List::add)。
  • BinaryOperator<A> combiner(): 合并两个并行计算的累加器(用于并行流,如 (list1, list2) -> { list1.addAll(list2); return list1; })。
  • Function<A, R> finisher(): 将累加器转换为最终结果(如 Function.identity() 表示直接返回累加器本身)。

可选方法:

  • Set<Characteristics> characteristics(): 返回特征集合(如 CONCURRENT, UNORDERED, IDENTITY_FINISH),用于优化性能。

要理解上面这些方法,需要去查看SupplierBiConsumerBinaryOperatorFunction的定义。以下是每个方法的详细解析和对应的函数式接口说明:

1.Supplier<A> supplier()

@FunctionalInterface
public interface Supplier<T> {
    T get(); // 无参数,返回一个结果
}

作用: 创建初始容器:在流处理开始时,提供一个空的累加器实例(类型为 A)。

2.BiConsumer<A, T> accumulator()

@FunctionalInterface
public interface BiConsumer<T, U> {
    void accept(T t, U u); // 接受两个参数,无返回值
}

作用: 元素累积:定义如何将流中的元素(类型 T)合并到累加器(类型 A)中。

3.BinaryOperator<A> combiner()

@FunctionalInterface
public interface BinaryOperator<T> extends BiFunction<T, T, T> {
    T apply(T t1, T t2); // 接受两个同类型参数,返回同类型结果
}

作用: 并行合并:在并行流中,将两个部分结果(类型 A)合并为一个。

4.Function<A, R> finisher()

@FunctionalInterface
public interface Function<T, R> {
    R apply(T t); // 接受一个参数,返回转换后的结果
}

作用:最终转换:将累加器(类型 A)转换为最终结果(类型 R);恒等函数:如果 A 和 R 相同,可直接用 Function.identity(),直接返回累加器本身。下面是Function.identity()的定义:

static <T> Function<T, T> identity() {
	return t -> t;
}

简化实现方式:
通常不需要手动实现,而是通过 Collectors 工具类直接使用预定义的收集器(如 toList(), joining())。 若需自定义,可用 Collector.of() 快速创建:

// Collectors.toList() 的简化实现
List<Integer> list = Stream.of(1, 2, 3)
    .collect(Collector.of(
        ArrayList::new,            // supplier
        List::add,                 // accumulator
        (left, right) -> {         // combiner
            left.addAll(right);
            return left;
        },
        Characteristics.IDENTITY   // 无需finisher(直接返回累加器)
    ));

典型用途:

List<String> list = stream.collect(Collectors.toList());  // 内置收集器

理解这个接口后,可以灵活实现自定义的聚合操作(如复杂统计、分组等)。

Collectors工具类

java.util.stream.Collectors是一个工厂类,提供了大量预定义的Collector实现。这些静态方法覆盖了90%以上的收集需求,让我们无需手动实现Collector接口。

常用Collectors方法速查表

方法 功能 示例
toList() 收集到List collect(Collectors.toList())
toSet() 收集到Set collect(Collectors.toSet())
toMap() 收集到Map collect(toMap(k, v))
joining() 连接字符串 collect(joining(", "))
groupingBy() 分组收集 collect(groupingBy(User::getCity))
partitioningBy() 分区收集 collect(partitioningBy(u -> u.getAge() > 18))
summingInt() 整数求和 collect(summingInt(Product::getPrice))
averagingDouble() 求平均值 collect(averagingDouble(Student::getScore))

实际应用示例

1. 基础收集:转List/Set

List<String> names = employees.stream()
    .map(Employee::getName)
    .collect(Collectors.toList()); 

Set<String> uniqueNames = employees.stream()
    .map(Employee::getName)
    .collect(Collectors.toSet());

2. 分组收集:groupingBy()

// 按部门分组
Map<Department, List<Employee>> byDept = employees.stream()
    .collect(Collectors.groupingBy(Employee::getDepartment));

// 多级分组:先按部门,再按职位
Map<Department, Map<Position, List<Employee>>> multiLevel = employees.stream()
    .collect(groupingBy(Employee::getDepartment, 
             groupingBy(Employee::getPosition)));

3. 分区收集:partitioningBy()

// 将员工分为管理者与非管理者
Map<Boolean, List<Employee>> partitioned = employees.stream()
    .collect(Collectors.partitioningBy(e -> e.isManager()));

4. 统计操作

// 计算平均工资
double avgSalary = employees.stream()
    .collect(Collectors.averagingDouble(Employee::getSalary));

// 获取工资汇总统计
DoubleSummaryStatistics stats = employees.stream()
    .collect(Collectors.summarizingDouble(Employee::getSalary));
// stats: count, sum, min, max, average

5. 自定义收集器

当内置收集器不满足需求时,可以自定义Collector:

1.自定义字符串连接收集器

Collector<String, StringBuilder, String> concatCollector = Collector.of(
    StringBuilder::new,            // supplier
    StringBuilder::append,         // accumulator
    (sb1, sb2) -> sb1.append(sb2), // combiner
    StringBuilder::toString,       // finisher
    Characteristics.IDENTITY_FINISH
);

2.统计平均值

Collector<Integer, int[], Double> avgCollector = Collector.of(
    () -> new int[2],             // [0] = sum, [1] = count
    (a, i) -> { a[0] += i; a[1]++; },
    (a1, a2) -> { 
        a1[0] += a2[0]; 
        a1[1] += a2[1]; 
        return a1; 
    },
    a -> (double) a[0] / a[1]
);

Collector执行流程解析

理解Collector如何工作:

  • 创建容器:调用supplier()创建新容器

  • 累积元素:对每个元素调用accumulator()

  • 合并结果(并行流):使用combiner()合并部分结果

  • 最终转换:通过finisher()转换为最终形式

graph LR A[Supplier 创建容器] --> B[Accumulator 累积元素] B --> C{是否并行流} C -->|是| D[Combiner 合并结果] C -->|否| E[Finisher 转换结果] D --> E E --> F[最终结果]

下面是个示例:

// 自定义字符串拼接收集器
Collector<String, StringBuilder, String> concatCollector = Collector.of(
    StringBuilder::new,               // supplier: 创建空StringBuilder
    (sb, s) -> sb.append(s),          // accumulator: 逐个拼接字符串
    (sb1, sb2) -> sb1.append(sb2),    // combiner: 合并两个StringBuilder
    StringBuilder::toString           // finisher: 转为String
);

// 使用示例
String result = Stream.of("a", "b", "c", "d").collect(concatCollector);
System.out.println(result); // 输出 "abcd"

流程拆解(以并行流为例): 假设输入流为 ["a", "b", "c", "d"],并行度为 2:

  • 分区处理:
    线程1:supplier() → 新建 StringBuilder sb1,处理 ["a", "b"]sb1 = "ab"
    线程2:supplier() → 新建 StringBuilder sb2,处理 ["c", "d"]sb2 = "cd"
  • 合并结果:
    combiner() 被调用 → sb1.append(sb2)sb1 = "abcd"
  • 最终转换:
    finisher() 被调用 → sb1.toString() → 返回 "abcd"

核心方法协作流程表格汇总:

阶段 方法 顺序流调用次数 并行流调用次数
初始化 supplier() 1 次 每个线程 1 次
累积 accumulator() 每个元素 1 次 每个元素在各自线程 1 次
合并 combiner() 0 次 线程数-1 次(合并子结果)
转换 finisher() 1 次(除非有标识特性) 1 次

characteristics() 方法详解

Collector 接口中的 characteristics() 方法通过返回一组 Characteristics 枚举值来声明收集器的行为特性,这些特性会直接影响 并行流(Parallel Stream) 的执行效率和正确性。以下是三种特性对性能优化的具体影响:

1.CONCURRENT(并发性)
作用:表示累加器(accumulator)支持多线程并发操作同一个容器。

优化场景:

  • 并行流中,所有线程共享同一个累加器容器(而非每个线程创建自己的容器),避免了 combiner 的合并开销。
  • 适用于线程安全的容器(如 ConcurrentHashMap)。

风险:

  • 如果容器非线程安全(如 ArrayList),会导致数据竞争(需手动同步)。

示例:

// 使用线程安全的容器(如 ConcurrentHashMap)
Collector.of(
 ConcurrentHashMap::new,
 (map, element) -> map.put(element, 1),
 (map1, map2) -> { map1.putAll(map2); return map1; },
 Characteristics.CONCURRENT
);

2.UNORDERED(无序性)
作用:表示结果不依赖元素的处理顺序。

优化场景:

  • 并行流可以忽略元素的原始顺序,减少排序开销(如 HashSet 的收集比 LinkedHashSet 更快)。
  • 与 CONCURRENT 结合时,能进一步减少线程间的协调开销。

示例:

// 收集到 HashSet(无序)比 LinkedHashSet(有序)更高效
Collector.of(
 HashSet::new,
 Set::add,
 (set1, set2) -> { set1.addAll(set2); return set1; },
 Characteristics.UNORDERED
);

3.IDENTITY_FINISH(恒等完成)
作用:表示 finisher() 是恒等函数(即最终结果 R 就是累加器 A)。

优化场景:

  • 跳过 finisher() 的调用,减少一次转换开销。
  • 适用于 A 和 R 类型相同的场景(如 toList() 直接返回 ArrayList)。

示例:

// toList() 的简化实现:无需 finisher()
Collector.of(
 ArrayList::new,
 List::add,
 (list1, list2) -> { list1.addAll(list2); return list1; },
 Characteristics.IDENTITY_FINISH
);

通过合理声明 characteristics(),可以显著提升并行流的吞吐量,但需确保特性与实际行为一致。

posted @ 2021-10-13 08:43  飞鸿影  阅读(772)  评论(0)    收藏  举报