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支持并发操作,提高性能。