java8的stream api能很方便我们对数据进行统计分类等工作,以前我们写的很多统计数据的代码往往是循环迭代得到的,不说别人看不懂,自己的代码放久了也要重新看一段时间才能看得懂。现在,java8吸收了适合科学计算的语言的新特性,提供了stream api,让我们方便并且直观地编写统计代码成为可能。
stream里有一个collect(Collector c)方法,需要传入Collector收集器这个接口。现在就说说这个接口定义的职责。
public interface Collector<T, A, R> {
Supplier<A> supplier();
BiConsumer<A, T> accumulator();
BinaryOperator<A> combiner();
Function<A, R> finisher();
Set<Characteristics> characteristics();
}
Collector主要定义了容器的类型,添加元素的方法,容器合并的方法还有输出的结果。
supplier就是生成容器,accumulator是添加元素,combiner是合并容器,finisher是输出的结果,characteristics是定义容器的三个属性,包括是否有明确的finisher,是否需要同步,是否有序。
例如:Collectors里面有个tolist()方法,返回一个收集器如下
Collector(ArrayList::new,List::add,List::addAll,IDENTITY_FINISH)
可以看到,先是一个ArrayList的实例化,然后是添加元素使用List的add方法,容器合并就是addAll方法。这里没有finisher,原因是最终结果要的就是List,finisher就是返回容器。但是stream.collect()方法如何得知?就是通过枚举类型得到的。三个枚举类型:
IDENTITY_FINISH 不用finisher,doc的描述为elided(可以省略的)
UNORDERED 集合是无序的
CONCURRENT 集合的操作需要同步
定义好collector,最终传参进stream的collect方法里,这个终结的操作最后会通过你定义的统计和收集的操作进行收集。jdk源码中有一个Collectors类已经为我们定义好很多操作,我们只要简单的添加一些收集的定义就能为我们很好的工作了。
这里重点讲一个groupingBy()方法。若果我们学过sql语句的话会了解到,groupby这个方法我们会常常用到。现在我们通过看源码了解这个方法是怎么实现的。这个方法的最终样貌是下面
public static <T, K, D, A, M extends Map<K, D>>
Collector<T, ?, M> groupingBy(Function<? super T, ? extends K> classifier,
Supplier<M> mapFactory,
Collector<? super T, A, D> downstream)
当你使用的是
groupingBy(e->e.getName())
其实调用了
groupingBy(e->e.getName(),toList())
而最后调用了
groupingBy(e->e.getName(),HashMap::new,toList())
也就是上面长长的一坨。究竟这个groupingBy方法给我们构造了一个怎样的Collector呢?现在我们分析一下。
以上面为例子,我们知道最后结果承载的容器是Map,更加准确的说,是一个HashMap。所以
supplier = HashMap::new
然后是accmulator,首先通过e->e.getName()获得Map的key,然后生成或取出key对应的value,然后对于toList()来说,value是List,然后将元素加进List中,可以得到
accmulator = (map,elemet)->
{
1. 得到key,
2. 从map中通过key获得container,没有container的话实例化一个新的container
(通过downstream.supplier得到List),
3. 对container执行downstream.accmulator方法,也就是add方法
}
上面的downstream就是toList方法给我们返回的collector,我们称之为下游收集器。
接着是combiner也使用了downstream.combiner,不过容器是map,得到
mapmerger(downstream.combiner)即是addAll
这里需要参考map的默认方法mapmerger方法。这个方法的大致意思是对map中每一个key进行遍历。
private static <K, V, M extends Map<K,V>>
BinaryOperator<M> mapMerger(BinaryOperator<V> mergeFunction) {
return (m1, m2) -> {
for (Map.Entry<K,V> e : m2.entrySet())
m1.merge(e.getKey(), e.getValue(), mergeFunction);
return m1;
};
}
最后我们就能得到这么一个收集器
Collector(supplier = HashMap::new,
accmulator = { 得到key,得到container,使用下游的accmulator}
combiner = mapmerger(downstream.combiner)
)
finisher和characteristics不详细说明,因为一般来说finisher可以省略,当不能省略的时候,就是有下游收集器的finisher,源码里面有体会,需要理解清楚的可以认真源码。
再给一个例子:
collect(groupingBy(e->e.getArtist(),mapping(artist->artist.getName())))
最后得到的收集器
Collector(supplier = HashMap::new,
accmulator = { 得到key,得到container,
使用下游的accmulator.accept(container,artist->artist.getName())
}
combiner = mapmerger(downstream.combiner)
)
-------------------------------------------------------------------------------------------------------------------------------------------------------------- 这是快乐的分割线 ------------------------------------------------------------------------------------------------------------------------------------------------------------------
总结:
收集器功能强大,一般来说,jdk自带的Collectors里面的方法已经能满足一般需求,而了解Collectors的内部,让我们更加了解如何使用它。在写这篇博文的时候,我对collector的印象也加深了,所以建议大家也写一下blog,自己也会受益良多。不断学习新特性,有机会的话把它们加入到代码里。
希望这篇博文能让你有一丢丢收获!
来源:oschina
链接:https://my.oschina.net/u/2354474/blog/487322