如何自定义一个Collector

Java 1.8提供的Collectors类提供了很多方便的接口,假如现有接口不能满足需求,应该如何定制一个Collector呢?


  Collector提供了一个默认实现,通过调用of方法即可。

    public static<T, A, R> Collector<T, A, R> of(Supplier<A> supplier,
                                                 BiConsumer<A, T> accumulator,
                                                 BinaryOperator<A> combiner,
                                                 Function<A, R> finisher,
                                                 Characteristics... characteristics) {
        ...
        Set<Characteristics> cs = Collectors.CH_NOID;
        if (characteristics.length > 0) {
            cs = EnumSet.noneOf(Characteristics.class);
            Collections.addAll(cs, characteristics);
            cs = Collections.unmodifiableSet(cs);
        }
        return new Collectors.CollectorImpl<>(supplier, accumulator, combiner, finisher, cs);
    }
  • supplier提供一个对象用于输入参数,进行累加操作;
  • accumulator提供累加操作的实现;
  • combiner用于多个输入对象的合并,在普通串行(sequential)的情况下只有一个输入对象可以忽略,假如steam使用了并发操作(parallel)时就必须进行对象合并了;
  • finisher用于将计算结果转化为我们最终需要的类型;
  • characteristics用于指定操作的优化类型;

  值得注意的是,注释中提到提供的输入参数类型必须为可变类型(mutable);

@param <A> the mutable accumulation type of the reduction operation (often hidden as an implementation detail)

  先来看看Collectors类源代码是怎么实现Collector接口的,以常用的toList方法为例,

static final Set<Collector.Characteristics> CH_ID = Collections.unmodifiableSet(EnumSet.of(Collector.Characteristics.IDENTITY_FINISH));

public static <T> Collector<T, ?, List<T>> toList() {
    return new CollectorImpl<>((Supplier<List<T>>) ArrayList::new, 
                               List::add,
                               (left, right) -> { 
                                    left.addAll(right); 
                                    return left; 
                               },
                               CH_ID);
}

  在toList方法中,supplier为ArrayList::new,提供了一个ArrayList用于累加的容器,使用List::add作为accumulator累加操作,combiner实现中调用List的addAll合并两个列表,
由于最终类型就是List,因此toList忽略finisher,使用IDENTITY_FINISH优化类型,标明不需要进行finisher操作直接返回计算结果。


  假设有个需求,要求统计A类列表的总数量

 public class A {
    
    private int count;

    public A(int count) {
        this.count = count;
    }

    public int getCount() {
        return count;
    }
}

  这里可以简单的使用mapToInt或者Collectors类提供的summingInt方法实现。

var aList = new ArrayList<A>();
...
int totalUseMap = aList.stream().mapToInt(A::getCount).sum();
// or
int totalUseCollect = aList.stream().collect(Collectors.summingInt(Obj::getCount));

  假如使用自定义Collector的话,应该如何实现呢?

var aList = new ArrayList<A>();
...
int total = aList.parallelStream().collect(Collector.of(() -> new int[1],
                                                (result, a) -> result[0] += a.getCount(),
                                                (a, b) -> {
                                                    a[0] += b[0];
                                                    return a;
                                                },
                                                result -> result[0],
                                                Collector.Characteristics.CONCURRENT));

  supplier必须提供可变对象,这里不能简单的提供() -> 0,又因为提供结果类型为int,与默认的输入参数int[]类型不一致,因此必须设置finisher将结果转化为int类型,
由于accumulator并不影响并发,因此设置characteristics支持并发操作,提高性能。

猜你喜欢

转载自www.cnblogs.com/yeyu456/p/11989802.html