Lamabda表达式之重构和定制收集器

reduce模式

前面介绍过很多lamabda表达式的用法,传送门

JAVA8-lambda表达式1

JAVA8-lambda表达式2-集合类api

JAVA8-lambda表达式-并行流,提升效率的利器?

JAVA8-lambda表达式之Optional

现在介绍一种比较通用的编程模式,reduce:reduce 操作可以实现从一组值中生成一个值。比如因为常用而被纳入标准库中
count、 min 和 max 方法

@Test
    public void test1() {
        // count测试
        List list = Stream.of(1,2,3,4,5).collect(Collectors.toList());
        // 计算集合中值大于3的个数
        long result = list.stream().filter(v -> v> 3).count();
        assert result == 2;

        // 找出集合中最大值
        Integer maxV = list.stream().max(Integer::compareTo).get();
        assert maxV == 5;

        // 找出集合中最大值
        Integer minV = list.stream().min(Integer::compareTo).get();
        assert minV == 1;
    }

这些方法都是 reduce 操作。

再看一个例子,现在要将上面的数据里面的值进行求和,即1+2+3+4+5=?

如果是传统的写法怎么做呢,用for循环!

@Test
    public void test2() {
        // 求和
        List list = Stream.of(1,2,3,4,5).collect(Collectors.toList());
        Integer total = 0;
        for (Integer v : list) {
            total = total + v;
        }
        assert total == 15;
    }

再看一下reduce的写法,Lambda 表达式就是 reducer, 它执行求和操作, 有两个参数: 传入 Stream 中的当前元素和 acc。 将两个参数相加, acc 是累加器, 保存着当前的累加结果。

total = list.stream().reduce(0, (acc, ele) -> acc + ele).intValue();
        assert total == 15;

定制收集器

常见的需求

一个工单审核流程中,把审批人员名字,格式化输出

传统的for循环写法

@Test
    public void test4() {
        List names = Arrays.asList("张三","李四","王五");
        StringBuffer sb = new StringBuffer();
        sb.append("[");
        for (String name : names) {
            if (sb.length() > 1) {
                sb.append(", ");
                sb.append(name);
            }
        }
        sb.append("]");
        System.out.println(sb);
    }

输出:[张三, 李四, 王五]

Lambda写法

public void test5() {
        List names = Arrays.asList("张三","李四","王五");
        StringBuffer sb = new StringBuffer();
        sb.append("[");
        names.stream().forEach(name -> {
            if (sb.length() > 1) {
                sb.append(", ");
            }
            sb.append(name);
        });
        sb.append("]");
        System.out.println(sb);
    }

用了lambda的forEach,不过在这里,forEach还是有点重了,没有达到用高级收集器简化代码的目的,再用reduce试试

reduce写法

public void test6() {
        List names = Arrays.asList("张三", "李四", "王五");
        StringBuffer sb = names.stream().reduce(new StringBuffer(), (builder, name) -> {
                    if (builder.length() > 1) {
                        builder.append(", ");
                    }
                    builder.append(name);
                    return builder;
                }, (left, right) ->
                        left.append(right)
        );
        sb.insert(0, "[");
        sb.append("]");
        System.out.println(sb);
    }

上面3种写法都能实现结果,但是不知道有没有发现,lambda表达式,不管是forEach,reduce并没有使代码易读,反而理难理解了。所以定制一个收集器来实现这个需求

定制收集器

实现 Collector 接口

import java.util.HashSet;
import java.util.Set;
import java.util.StringJoiner;
import java.util.function.BiConsumer;
import java.util.function.BinaryOperator;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collector;

public class StringCollector implements Collector {

    private String delimiter;
    private String prefix;
    private String suffix;

    public String getPrefix() {
        return prefix;
    }

    public String getDelimiter() {
        return delimiter;
    }

    public String getSuffix() {
        return suffix;
    }
    public StringCollector(String delimiter, String prefix, String suffix) {
        this.delimiter = delimiter;
        this.prefix = prefix;
        this.suffix = suffix;
    }

    /**
     * A function that creates and returns a new mutable result container.
     *
     * @return a function which returns a new, mutable result container
     */
    @Override
    public Supplier supplier() {
        return () -> new StringJoiner(delimiter, prefix, suffix);
    }

    /**
     * A function that folds a value into a mutable result container.
     *
     * @return a function which folds a value into a mutable result container
     */
    @Override
    public BiConsumer accumulator() {
        return StringJoiner::add;
    }

    /**
     * A function that accepts two partial results and merges them.  The
     * combiner function may fold state from one argument into the other and
     * return that, or may return a new result container.
     *
     * @return a function which combines two partial results into a combined
     * result
     */
    @Override
    public BinaryOperator combiner() {
        return StringJoiner::merge;
    }

    /**
     * Perform the final transformation from the intermediate accumulation type
     * {@code A} to the final result type {@code R}.
     *
     * 

If the characteristic {@code IDENTITY_TRANSFORM} is * set, this function may be presumed to be an identity transform with an * unchecked cast from {@code A} to {@code R}. * * @return a function which transforms the intermediate result to the final * result */ @Override public Function finisher() { return StringJoiner::toString; } /** * Returns a {@code Set} of {@code Collector.Characteristics} indicating * the characteristics of this Collector. This set should be immutable. * * @return an immutable set of collector characteristics */ @Override public Set characteristics() { return new HashSet(); } }

由于 Collector 接口支持泛型, 因此先得确定一些具
体的类型:

  • 待收集元素的类型, 这里是 String;
  • 累加器的类型 StringJoiner;
  • 最终结果的类型, 这里依然是 String。

然后来使用它

@Test
    public void test7() {
        List names = Arrays.asList("张三", "李四", "王五");
        String result = names.stream().collect(new StringCollector(",","[", "]"));
        System.out.println(result);
    }

输出结果与前面一致:[张三,李四,王五]

到此,一个定制化的收集器就开发完成了。

一个收集器由四部分组成

  • 首先是一个 Supplier, 这是一个工厂方法, 用来创建容器, 在这个例子中, 就是 StringJoiner。 和 reduce 操作中的第一个参数类似, 它是后续操作的初值
/**
     * A function that creates and returns a new mutable result container.
     *
     * @return a function which returns a new, mutable result container
     */
    @Override
    public Supplier supplier() {
        return () -> new StringJoiner(delimiter, prefix, suffix);
    }

所以在StringCollector声明需要传入的参数:分割符-delimiter,前缀prefix,后缀-suffix及对应的构造函数

 private String delimiter;
    private String prefix;
    private String suffix;

    public String getPrefix() {
        return prefix;
    }

    public String getDelimiter() {
        return delimiter;
    }

    public String getSuffix() {
        return suffix;
    }
    public StringCollector(String delimiter, String prefix, String suffix) {
        this.delimiter = delimiter;
        this.prefix = prefix;
        this.suffix = suffix;
    }
  • 收集器的 accumulator 的作用和 reduce 操作的第二个参数一样, 它结合之前操作的结果和当前值, 生成并返回新的值
/**
     * A function that folds a value into a mutable result container.
     *
     * @return a function which folds a value into a mutable result container
     */
    @Override
    public BiConsumer accumulator() {
        return StringJoiner::add;
    }
  • combine 方法很像 reduce 操作的第三个方法。 如果有两个容器, 我们需要将其合并
/**
     * A function that accepts two partial results and merges them.  The
     * combiner function may fold state from one argument into the other and
     * return that, or may return a new result container.
     *
     * @return a function which combines two partial results into a combined
     * result
     */
    @Override
    public BinaryOperator combiner() {
        return StringJoiner::merge;
    }
  • 上面已经将流中的值叠加入一个可变容器中, 但这还不是想要的最终结果。 这里调用了 finisher 方法, 以便进行转换。
    /**
         * Perform the final transformation from the intermediate accumulation type
         * {@code A} to the final result type {@code R}.
         *
         * 

    If the characteristic {@code IDENTITY_TRANSFORM} is * set, this function may be presumed to be an identity transform with an * unchecked cast from {@code A} to {@code R}. * * @return a function which transforms the intermediate result to the final * result */ @Override public Function finisher() { return StringJoiner::toString; }

  • 特征是一组描述收集器的对象, 框架可以对其适当优化。 characteristics 方法定义了特征
    /**
         * Returns a {@code Set} of {@code Collector.Characteristics} indicating
         * the characteristics of this Collector.  This set should be immutable.
         *
         * @return an immutable set of collector characteristics
         */
        @Override
        public Set characteristics() {
            return new HashSet();
        }

     

你可能感兴趣的:(JAVA,不得不学,jdk源码之路)