17

写这个文章其实主要是因为刚有个童鞋问了个问题https://segmentfault.com/q/10...
正写的带劲安利Java8的实现方式,结果还没写完...无意发现问题被关闭了...哎...都写了一半了...又不想放弃,就干脆写成文章

问题主要就是把集合里的数据按照一定大小顺序平均分成若干组的问题,看起来挺简单的,不过我开始看到就想用用stream来实现,但是想了想ollectors里并没有适合的方法,所以就想到了用定制的collector来实现了。
原问题的截图:

clipboard.png

正式开始回答(我是直接把之前的回答copy过来的哈):

集合处理的话,我还是推荐Java8stream,题主这个问题设计到分组,那自然就要涉及到streamcollect方法了,这个方法是收集数据的意思,该方法的参数就是一个Collector接口,只要传入一个Collector的实现类就可以了,常用的实现比如在工具类Collectors里有toList,toMap等,已经帮你默认写了收集为集合或者Map的实现类了,但是明显这些实现类都不合适,所以这里需要定制一个Collector接口的实现啦

其实就是仿照Collectors里的内部类CollectorImpl写一个就是了...

=====================(Collector介绍,如果你已经清楚可以略过的...)==================

介绍哈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通过一种方式变成R

了解了泛型的意思,咱们结合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);
    }

官方写的很简单,很随意...

前三个参数分别对应了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));
}

这样代码就非常漂亮了~哈哈哈~~


imango
3k 声望113 粉丝