JAVA 8函数式编程(五):收集器详解与自定义实现

在Stream的接口中,最强大的接口就是collect了,当然这也是最难的接口,它对Stream的应用过程可分为四步:
1. 利用Supplier接口,创建存储中间运算结果的容器(不是返回结果),扮演的角色为supplier;
2. 利用BiConsumer

//  声明单词集合对象
List<String> words = Lists.newArrayList("hi", "Hello", "Hi", "hello", "hI");
//  定义Supplier接口,存储中间运算结果
Supplier<Map<String, Integer>> supplier = () -> new HashMap<String, Integer>();
//  定义累加器
BiConsumer<Map<String, Integer>, String> accumulator = (t, u) -> {
    String lower = u.toLowerCase();
    //  依次添加元素
    Integer num = t.get(lower);
    if(num == null) {
        num = 1;
    } else {
        num = num + 1;
    }
    t.put(lower, num);
};
//  并行会对输入数据进行切割,会创建利用Supplier与BiConsumer的多个实例,所以需要进行合并
BinaryOperator<Map<String, Integer>> combiner = (a, b) -> {
    Map<String, Integer> result = Maps.newHashMap(a);
    for(String key : b.keySet()) {
        Integer num = b.get(key) == null ? 0 : b.get(key);
        Integer _num = result.get(key) == null ? 0 : result.get(key);
        result.put(key, num + _num);
    }
    return result;
};
//  这一个功能只是为了把所有的步骤走完
Function<Map<String, Integer>, Integer> finisher = t -> t.keySet().size();
//  
Collector<String, Map<String, Integer>, Integer> collector = Collector.of(supplier, accumulator, combiner, finisher);
//  并行执行
Integer wc = words.stream().parallel().collect(collector);
System.out.println(wc);

为了提高对输入数据的处理速度,我们都会采用并行计算的方式以加强CPU的负载,此时请务必对数据进行合并,否则将会出现错误的结果。

简化与封装

从上面的例子可以看出,中间结果容器贯穿始终,这也使我们花费了大量的代码在描述此容器,并且代码散落在各处,复用性较差,为了解决此问题,我们可以将中间结果容器融入到对象中,充分利用对象的上下文,大幅简化方法的签名。

在展示封装代码之前,我们再强调一遍Collector

static class WordCounter {

        private Map container = new HashMap<>();

        //  与接口BiConsumer的accept一致,省略A参数,无返回值,承担accumulator角色
        public void add(String world) {
            String lower = world.toLowerCase();
            Integer num = container.get(lower);
            if(num == null) {
                num = 1;
            } else {
                num = num + 1;
            }
            container.put(lower, num);
        }

        //  BinaryOperator的apply()一致,依旧省略一个A参数,返回类型为A,承担combiner角色
        public WordCounter merge(WordCounter other) {
            Map result = Maps.newHashMap(container);
            for(String key : other.container.keySet()) {
                Integer num = other.container.get(key) == null ? 0 : other.container.get(key);
                Integer _num = container.get(key) == null ? 0 : container.get(key);
                result.put(key, num + _num);
            }
            this.container = result;
            return this;
        }

        //  返回结果,与Function的apply()一致,依旧省略一个A参数,返回类型为R,承担finisher完成器角色
        public int count() {
            for(String key : container.keySet()) {
                System.out.println(key + ":" + container.get(key));
            }
            return this.container.size();
        }
}

现在我们可以利用封装的类及其方法提炼出更简单的写法,如下:

//  请仔细注意收集器的泛型顺序
Collector coll = Collector.of(WordCounter::new, WordCounter::add, WordCounter::merge, WordCounter::count);
Integer counter = words.stream().parallel().collect(coll);
System.out.println(counter);

结论

收集器(Collector)相当于是对其他函数功能的一个合集,如map、reduce、consume、merge、supply等,只有掌握了此方法,才算真正掌握了Stream操作,在简化与提炼的过程中,我们可以把中间结果容器融入到对象的上下文中,一是增加了代码的复用性,二是提高了代码的封装性。

你可能感兴趣的:(java)