寫這個文章其實主要是由於剛有個童鞋問了個問題https://segmentfault.com/q/10...
正寫的帶勁安利Java8
的實現方式,結果還沒寫完...無心發現問題被關閉了...哎...都寫了一半了...又不想放棄,就乾脆寫成文章segmentfault
問題主要就是把集合裏的數據按照必定大小順序平均分紅若干組的問題,看起來挺簡單的,不過我開始看到就想用用stream來實現,可是想了想Collectors裏並無適合的方法,因此就想到了用定製的collector來實現了。
原問題的截圖:ide
正式開始回答(我是直接把以前的回答copy過來的哈):工具
集合處理的話,我仍是推薦Java8
的stream
,題主這個問題設計到分組,那天然就要涉及到stream
的collect
方法了,這個方法是收集數據的意思,該方法的參數就是一個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
T
:stream
在調用collect
方法收集前的數據類型A
:A
是T
的累加器,遍歷T
的時候,會把T
按照必定的方式添加到A中,換句話說就是把一些T
經過一種方式變成A
R
:R
能夠當作是A
的累加器,是最終的結果,是把A
匯聚以後的數據類型,換句話說就是把一些A
經過一種方式變成R
code
瞭解了泛型的意思,我們結合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()
怎麼把一個累加器和另外一個累加器合併起來(這裏對應的是如何把List
和List
合併起來,固然是調用addAll
,這裏因爲最終要返回List,因此A和R是一個類型,都是List因此才調用addAll
)
再來看看第四個方法Function<A, R> finisher()
,其實就是怎麼把A
轉化爲R
,因爲是toList
,因此A
和R
是同樣的類型,這裏其實用就是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)); }
這樣代碼就很是漂亮了~哈哈哈~~