Java8 collector接口的定製實現

寫這個文章其實主要是由於剛有個童鞋問了個問題https://segmentfault.com/q/10...
正寫的帶勁安利Java8的實現方式,結果還沒寫完...無心發現問題被關閉了...哎...都寫了一半了...又不想放棄,就乾脆寫成文章segmentfault

問題主要就是把集合裏的數據按照必定大小順序平均分紅若干組的問題,看起來挺簡單的,不過我開始看到就想用用stream來實現,可是想了想Collectors裏並無適合的方法,因此就想到了用定製的collector來實現了。
原問題的截圖:ide

clipboard.png

正式開始回答(我是直接把以前的回答copy過來的哈):工具

集合處理的話,我仍是推薦Java8stream,題主這個問題設計到分組,那天然就要涉及到streamcollect方法了,這個方法是收集數據的意思,該方法的參數就是一個Collector接口,只要傳入一個Collector的實現類就能夠了,經常使用的實現好比在工具類Collectors裏有toList,toMap等,已經幫你默認寫了收集爲集合或者Map的實現類了,可是明顯這些實現類都不合適,因此這裏須要定製一個Collector接口的實現啦測試

其實就是仿照Collectors裏的內部類CollectorImpl寫一個就是了...this

=====================(Collector介紹,若是你已經清楚能夠略過的...)==================spa

介紹哈Collector<T, A, R>接口的方法,一共5個設計

Supplier<A> supplier()
BiConsumer<A, T> accumulator()
BinaryOperator<A> combiner()
Function<A, R> finisher()
Set<Characteristics> characteristics()

方法中有泛型,因此要先要介紹哈Collector中的三個泛型T, A, R
Tstream在調用collect方法收集前的數據類型
AAT的累加器,遍歷T的時候,會把T按照必定的方式添加到A中,換句話說就是把一些T經過一種方式變成A
RR能夠當作是A的累加器,是最終的結果,是把A匯聚以後的數據類型,換句話說就是把一些A經過一種方式變成Rcode

瞭解了泛型的意思,我們結合Collectors.toList構造的默認實現類的實現方式來看看Collector接口的方法對象

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);
    }

官方寫的很簡單,很隨意...blog

前三個參數分別對應了Collector的前三個方法,也就是

(Supplier<List<T>>) ArrayList::new 對應Supplier<A> supplier()第一個方法
List::add 對應BiConsumer<A, T> accumulator()第二個方法
(left, right) -> { left.addAll(right); return left; }對應BinaryOperator<A> combiner()第三個方法

因此對應着來看就清楚了
Supplier<A> supplier() 怎麼建立一個累加器(這裏對應的是如何建立一個List
BiConsumer<A, T> accumulator()怎麼把一個對象添加到累加器中(這裏對應的是如何在List裏添加一個對象,固然是調用add方法咯)
BinaryOperator<A> combiner()怎麼把一個累加器和另外一個累加器合併起來(這裏對應的是如何把ListList合併起來,固然是調用addAll,這裏因爲最終要返回List,因此A和R是一個類型,都是List因此才調用addAll

再來看看第四個方法Function<A, R> finisher(),其實就是怎麼把A轉化爲R,因爲是toList,因此AR是同樣的類型,這裏其實用就是Function.identity
最後第五個方法Set<Characteristics> characteristics()其實就是這個Collector的一些性質,toList這裏只用了Characteristics.IDENTITY_FINISH,表示第四個方法能夠不用設置,A類型就是最終的結果

=====================(Collector介紹完了)==================

如今建立自定義的collector,類名我就叫NumberCollectorImpl,因爲collector這裏要求有三個泛型,根據題主的需求,這三個泛型只有第一個是未知的,另外兩個應該是確認的List<List>結構,因此寫出來應該是這麼個效果

static class NumberCollectorImpl<T> implements Collector<T, List<List<T>>, List<List<T>>>

ok,針對collector要求實現的5個方法來依次說明
第一個方法Supplier<List<List<T>>> supplier(),很明顯應該就是ArrayList::new
第二個方法BiConsumer<List<List<T>>, T>,這個稍微麻煩點,起始應該寫成(list, item) -> {},主要就是補充{}中的代碼了
最開始的遍歷的時候,這個list實際上是父list,它確定是空的,因此這個時候要建立一個新子List,而後把item塞進子list中,最後再把建立的新子list放入到父list

if (list.isEmpty()){
  list.add(this.createNewList(item));
}

這裏簡單封了一個小方法createNewList,由於待會還要用

private List<T> createNewList(T item){
   List<T> newOne = new ArrayList<T>();
   newOne.add(item);
   return newOne;
}

若父list不爲空,那就要把當前父list中最後一個子list取出來,若空的話,固然又是要建立一個新子list而後按照以前的方法作,若不爲空,就判斷子list大小咯,若大小超過2,就再次建立一個新子list而後塞item,若沒有超過就在以前子list中塞入item,寫出來大概就是這個樣子

List<T> last = (List<T>) list.get(list.size() - 1);
if (last.size() < 2){
    last.add(item);
}else{
    list.add(this.createNewList(item));
}

第三個方法BinaryOperator<List<List<T>>> combiner(),其實就是兩個List如何合併,固然是addAll方法

(list1, list2) -> {
   list1.addAll(list2);
   return list1;
};

第四個方法Function<List<List<T>>, List<List<T>>> finisher(),因爲這個時候A和R的類型同樣,都是List<List<T>>,因此這裏直接就是Function.identity()

最後一個方法Set<Characteristics> characteristics()這裏直接能夠按照Collectors.toList來弄就好了,也就是直接採用Collections.unmodifiableSet(EnumSet.of(Collector.Characteristics.IDENTITY_FINISH))

綜上所述,完整代碼以下

/**
 * 自定義Collector
 *
 * @author imango
 * @since 2017/7/13
 */
public class CustomCollectors {

    // 默認採用2個一塊兒分組
    public static <T> Collector<T, List<List<T>>, List<List<T>>> groupByNumber(){
        return CustomCollectors.groupByNumber(2);
    }
    
    // 根據number的大小進行分組
    public static <T> Collector<T, List<List<T>>, List<List<T>>> groupByNumber(int number){
        return new NumberCollectorImpl(number);
    }

    /**
     * 個數分組器
     * @param <T>
     */
    static class NumberCollectorImpl<T> implements Collector<T, List<List<T>>, List<List<T>>> {
        // 每組的個數
        private int number;

        public NumberCollectorImpl(int number) {
            this.number = number;
        }

        @Override
        public Supplier<List<List<T>>> supplier() {
            return ArrayList::new;
        }

        @Override
        public BiConsumer<List<List<T>>, T> accumulator() {
            return (list, item) -> {
                if (list.isEmpty()){
                    list.add(this.createNewList(item));
                }else {
                    List<T> last = (List<T>) list.get(list.size() - 1);
                    if (last.size() < number){
                        last.add(item);
                    }else{
                        list.add(this.createNewList(item));
                    }
                }
            };
        }

        @Override
        public BinaryOperator<List<List<T>>> combiner() {
            return (list1, list2) -> {
                list1.addAll(list2);
                return list1;
            };
        }

        @Override
        public Function<List<List<T>>, List<List<T>>> finisher() {
            return Function.identity();
        }

        @Override
        public Set<Characteristics> characteristics() {
            return Collections.unmodifiableSet(EnumSet.of(Collector.Characteristics.IDENTITY_FINISH));
        }

        private List<T> createNewList(T item){
            List<T> newOne = new ArrayList<T>();
            newOne.add(item);
            return newOne;
        }
    }
}

外面那個類CustomCollectors 主要是爲了封裝NumberCollectorImpl類,之後也能夠把其餘自定義的收集器實現放在這裏面,並對外提供工具方法,而且我在NumberCollectorImpl類中新增了一個number成員變量,這樣就能夠自定義分組大小了,CustomCollectors提供了兩個對外方法groupByNumber,帶參數的那個就是能夠自定義分組個數的了,沒有參數的就是默認按照2個分組了,這樣的話,測試寫法就是這樣

public static void main(String[] args) {
   List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
   // 按照2個分組
   List<List<Integer>> twoNumberList = list.stream().collect(CustomCollectors.groupByNumber());
   // 按照5個分組
   List<List<Integer>> fiveNumberList = list.stream().collect(CustomCollectors.groupByNumber(5));
}

這樣代碼就很是漂亮了~哈哈哈~~

相關文章
相關標籤/搜索