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

这样代码就很是漂亮了~哈哈哈~~

相关文章
相关标签/搜索