资讯专栏INFORMATION COLUMN

Java8 collector接口的定制实现

zhangwang / 3376人阅读

摘要:写这个文章其实主要是因为刚有个童鞋问了个问题正写的带劲安利的实现方式,结果还没写完无意发现问题被关闭了哎都写了一半了又不想放弃,就干脆写成文章问题主要就是把集合里的数据按照一定大小顺序平均分成若干组的问题,看起来挺简单的,不过我开始看到就想

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

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

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

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

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

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

介绍哈Collector接口的方法,一共5个

Supplier supplier()
BiConsumer accumulator()
BinaryOperator combiner()
Function finisher()
Set characteristics()

方法中有泛型,所以要先要介绍哈Collector中的三个泛型T, A, R
Tstream在调用collect方法收集前的数据类型
AAT的累加器,遍历T的时候,会把T按照一定的方式添加到A中,换句话说就是把一些T通过一种方式变成A
RR可以看成是A的累加器,是最终的结果,是把A汇聚之后的数据类型,换句话说就是把一些A通过一种方式变成R

了解了泛型的意思,咱们结合Collectors.toList构造的默认实现类的实现方式来看看Collector接口的方法

public static 
    Collector> toList() {
        return new CollectorImpl<>((Supplier>) ArrayList::new, List::add,
                                   (left, right) -> { left.addAll(right); return left; },
                                   CH_ID);
    }

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

前三个参数分别对应了Collector的前三个方法,也就是

(Supplier>) ArrayList::new 对应Supplier supplier()第一个方法
List::add 对应BiConsumer accumulator()第二个方法
(left, right) -> { left.addAll(right); return left; }对应BinaryOperator
combiner()第三个方法

所以对应着来看就清楚了
Supplier
supplier() 怎么创建一个累加器(这里对应的是如何创建一个List
BiConsumer accumulator()怎么把一个对象添加到累加器中(这里对应的是如何在List里添加一个对象,当然是调用add方法咯)
BinaryOperator
combiner()怎么把一个累加器和另一个累加器合并起来(这里对应的是如何把ListList合并起来,当然是调用addAll,这里由于最终要返回List,所以A和R是一个类型,都是List所以才调用addAll

再来看看第四个方法Function finisher(),其实就是怎么把A转化为R,由于是toList,所以AR是一样的类型,这里其实用就是Function.identity
最后第五个方法Set characteristics()其实就是这个Collector的一些性质,toList这里只用了Characteristics.IDENTITY_FINISH,表示第四个方法可以不用设置,A类型就是最终的结果

=====================(Collector介绍完了)==================

现在创建自定义的collector,类名我就叫NumberCollectorImpl,由于collector这里要求有三个泛型,根据题主的需求,这三个泛型只有第一个是未知的,另外两个应该是确认的List结构,所以写出来应该是这么个效果

static class NumberCollectorImpl implements Collector>, List>>

ok,针对collector要求实现的5个方法来依次说明
第一个方法Supplier>> supplier(),很明显应该就是ArrayList::new
第二个方法BiConsumer>, T>,这个稍微麻烦点,起始应该写成(list, item) -> {},主要就是补充{}中的代码了
最开始的遍历的时候,这个list其实是父list,它肯定是空的,所以这个时候要创建一个新子List,然后把item塞进子list中,最后再把创建的新子list放入到父list

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

这里简单封了一个小方法createNewList,因为待会还要用

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

若父list不为空,那就要把当前父list中最后一个子list取出来,若空的话,当然又是要创建一个新子list然后按照之前的方法做,若不为空,就判断子list大小咯,若大小超过2,就再次创建一个新子list然后塞item,若没有超过就在之前子list中塞入item,写出来大概就是这个样子

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

第三个方法BinaryOperator>> combiner(),其实就是两个List如何合并,当然是addAll方法

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

第四个方法Function>, List>> finisher(),由于这个时候A和R的类型一样,都是List>,所以这里直接就是Function.identity()

最后一个方法Set characteristics()这里直接可以按照Collectors.toList来弄就行了,也就是直接采用Collections.unmodifiableSet(EnumSet.of(Collector.Characteristics.IDENTITY_FINISH))

综上所述,完整代码如下

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

    // 默认采用2个一起分组
    public static  Collector>, List>> groupByNumber(){
        return CustomCollectors.groupByNumber(2);
    }
    
    // 根据number的大小进行分组
    public static  Collector>, List>> groupByNumber(int number){
        return new NumberCollectorImpl(number);
    }

    /**
     * 个数分组器
     * @param 
     */
    static class NumberCollectorImpl implements Collector>, List>> {
        // 每组的个数
        private int number;

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

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

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

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

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

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

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

外面那个类CustomCollectors 主要是为了封装NumberCollectorImpl类,以后也可以把其他自定义的收集器实现放在这里面,并对外提供工具方法,并且我在NumberCollectorImpl类中新增了一个number成员变量,这样就可以自定义分组大小了,CustomCollectors提供了两个对外方法groupByNumber,带参数的那个就是可以自定义分组个数的了,没有参数的就是默认按照2个分组了,这样的话,测试写法就是这样

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

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

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/71143.html

相关文章

  • 乐字节-Java8新特性之Stream流(下)

    摘要:归约操作计算有效订单总金额有效订单总金额收集数据收集将流转换为其他形式,方法作为终端操作,接收一个接口的实现,用于给中元素做汇总的方法。 接上一篇:《Java8新特性之stream》,下面继续接着讲Stream 5、流的中间操作 常见的流的中间操作,归为以下三大类:筛选和切片流操作、元素映射操作、元素排序操作:showImg(https://segmentfault.com/img/b...

    20171112 评论0 收藏0
  • Java8中创建Stream 流四种方式以及 Stream 中间操作

    摘要:一创建里流的四种方式第一种通过得方法串行流或者方法并行流创建。终止操作时一次性全部处理,称为延迟加载筛选切片过滤中建操作。终止操作只有执行终止操作才会执行全部。即延迟加载结果中建操作。截断流,使其元素不超过给定数量。返回流中最大值。 Stream api **Stream api 是java8 中提供的对集合处理的api , 对数据进行一系列的中间操作,元数据不会发生改变 ...

    0xE7A38A 评论0 收藏0
  • Java8流特性和Lambda表达式

    摘要:表达式体现了函数式编程的思想,即一个函数亦可以作为另一个函数参数和返回值,使用了函数作参数返回值的函数被称为高阶函数。对流对象进行及早求值,返回值不在是一个对象。 Java8主要的改变是为集合框架增加了流的概念,提高了集合的抽象层次。相比于旧有框架直接操作数据的内部处理方式,流+高阶函数的外部处理方式对数据封装更好。同时流的概念使得对并发编程支持更强。 在语法上Java8提供了Lamb...

    gaara 评论0 收藏0
  • Stream流与Lambda表达式(二) Stream收集器 Collector接口

    摘要:一收集器接口陈杨收集器接口汇聚操作的元素类型即流中元素类型汇聚操作的可变累积类型汇聚操作的结果类型接口一种可变汇聚操作将输入元素累积到可变结果容器中在处理完所有输入元素后可以选择将累积的结果转换为最终表示可选操作归约操作 一、Stream收集器 Collector接口 package com.java.design.java8.Stream; import com.java.desi...

    or0fun 评论0 收藏0
  • 乐字节-Java8核心特性实战之Stream(流)

    摘要:大家好,我是乐字节的小乐。需要注意的是很多流操作本身就会返回一个流,所以多个操作可以直接连接起来,如下图这样,操作可以进行链式调用,并且并行流还可以实现数据流并行处理操作。为集合创建并行流。 大家好,我是乐字节的小乐。说起流,我们会联想到手机、电脑组装流水线,物流仓库商品包装流水线等等,如果把手机 ,电脑,包裹看做最终结果的话,那么加工商品前的各种零部件就可以看做数据源,而中间一系列的...

    wenshi11019 评论0 收藏0

发表评论

0条评论

zhangwang

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<