java8函數式編程--收集器collector

      java8的stream api能很方便咱們對數據進行統計分類等工做,之前咱們寫的不少統計數據的代碼每每是循環迭代獲得的,不說別人看不懂,本身的代碼放久了也要從新看一段時間才能看得懂。如今,java8吸取了適合科學計算的語言的新特性,提供了stream api,讓咱們方便而且直觀地編寫統計代碼成爲可能。java

      stream裏有一個collect(Collector c)方法,須要傳入Collector收集器這個接口。如今就說說這個接口定義的職責。sql

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

    Set<Characteristics> characteristics();
}

Collector主要定義了容器的類型,添加元素的方法,容器合併的方法還有輸出的結果。api


supplier就是生成容器,accumulator是添加元素,combiner是合併容器,finisher是輸出的結果,characteristics是定義容器的三個屬性,包括是否有明確的finisher,是否須要同步,是否有序。app


例如:Collectors裏面有個tolist()方法,返回一個收集器以下ide

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類已經爲咱們定義好不少操做,咱們只要簡單的添加一些收集的定義就能爲咱們很好的工做了。spa

      這裏重點講一個groupingBy()方法。若果咱們學過sql語句的話會了解到,groupby這個方法咱們會經常用到。如今咱們經過看源碼瞭解這個方法是怎麼實現的。這個方法的最終樣貌是下面code

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)

當你使用的是blog

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,本身也會受益良多。不斷學習新特性,有機會的話把它們加入到代碼裏。

但願這篇博文能讓你有一丟丟收穫!

相關文章
相關標籤/搜索