写这个文章其实主要是由于刚有个童鞋问了个问题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)); }
这样代码就很是漂亮了~哈哈哈~~