JAVA8 Stream 自定义收集器

JDK 提供的Collectors中有大部分常用的, 但是有些特殊情况需要自己自定义收集器, 以下就简单介绍一下自定义收集器的写法

import java.util.*;
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;

/**
 * @Description: <br/>
 * Collector<T, A, R>
 * T: 表示流元素的类型
 * A: 收集器类型
 * R: 结果类型
 * --------------------------------
 * 把一个字符串流, 收集为一个Map key/value 都为流中的元素
 * ["hello", "world"]  ->  [{ "hello": "hello"},{ "world": "world"}]
 * <p>
 * <br/>
 * @Author: Qz1997
 * @create 2020/12/7 19:35
 */
public class MySetToMapCollector<T extends String> implements Collector<T, Set<T>, Map<T, T>> {
    
    
    /**
     * 创建并返回新的可变结果收集器的函数
     * 注: 如果是串行流的话或者并行流  characteristics() 方法返回的set中有Characteristics.CONCURRENT
     * 那个就只会有一个结果容器
     *
     * @return 创建一个收集器类型
     */
    @Override
    public Supplier<Set<T>> supplier() {
    
    
        System.out.println("MySetCollector.supplier");
        ///return HashSet::new;
        return () -> {
    
    
            System.out.println("----------supplier.lambda执行了-----------");
            return new HashSet<>();
        };
    }

    /**
     * 累加器
     * 不断的将流中的元素 累加到收集器中
     * 就是讲T 累加到 A中
     *
     * @return 不断的将流中的元素 累加到收集器中的函数
     */
    @Override
    public BiConsumer<Set<T>, T> accumulator() {
    
    
        System.out.println("MySetCollector.accumulator");

        return (set, item) -> {
    
    
            System.out.println("result: " + set + " accumulator.for: " + Thread.currentThread().getName());
            set.add(item);
        };


    }

    /**
     * 用于并发流 讲多个线程的结果而合并成一个
     * 注意:
     * combiner() 方法调用的前提是调用的是并行流parallelStream
     * characteristics() 方法返回的set中没有Characteristics.CONCURRENT 该方法会被执行
     *
     * @return 合并的结果函数
     */
    @Override
    public BinaryOperator<Set<T>> combiner() {
    
    
        System.out.println("MySetCollector.combiner");
        return (set1, set2) -> {
    
    
            System.out.println("set1 = " + set1);
            System.out.println("set2 = " + set2);
            set1.addAll(set2);
            return set1;
        };
    }

    /**
     * 转换器
     * 这个是可选的
     * 这个方法就是将收集器转换成最终的结果类型
     * 对于大多数使用collector来说 收集器类型就是最终的结果类型
     * characteristics() 方法返回的set中没有IDENTITY_FINISH 该方法会被执行
     *
     * @return result
     */
    @Override
    public Function<Set<T>, Map<T, T>> finisher() {
    
    
        System.out.println("MySetCollector.finisher");
        return (set) -> {
    
    
            Map<T, T> map = new HashMap<>(16);
            set.forEach(o -> map.put(o, o));
            return map;
        };
    }

    /**
     * 表示这个收集器的特征
     * Characteristics
     * CONCURRENT:
     * 这个表示一个结果容器可以到个线程调用, 要保证线程安全 他和并行流是不一样的 如果这个收集器不是UNORDERED 那么仅能用于无序的数据源
     * UNORDERED:
     * 他并不保证输入元素的顺序 也就是无序的
     * IDENTITY_FINISH:
     * 标识的是: finisher 就是 identity函数 就是收集器的的类型和结果的类型相同 但是必须要保证 A 转换成 R 类型是成功的
     * ---------------------------------
     * 如果不满足以上三个条件 那么就直接返回一个空的set集合就可以了
     * 
     * @return result
     */
    @Override
    public Set<Characteristics> characteristics() {
    
    
        System.out.println("MySetCollector.characteristics");
        // 这是一个不可变的集合
        return Collections.unmodifiableSet(EnumSet.of(Characteristics.UNORDERED));
///          return Collections.unmodifiableSet(EnumSet.of(Characteristics.UNORDERED, Characteristics.CONCURRENT));
    }

    // ------------------------------测试代码-----------------------------

    public static void main(String[] args) {
    
    

        List<String> list = Arrays.asList("hello", "world", "welcome", "a", "as", "s", "f", "g", "j", "k", "l", "p", "1", "2");
        Set<String> set = new HashSet<>(list);
///        Map<String, String> collect = set.stream().collect(new MySetToMapCollector<>());
        /*
         * 如果使用parallelStream(并发流)  characteristics() 方法 声明了 CONCURRENT 那么 accumulator() 方法中 有两个线程 一个在向结果中添加元素
         * 别一个线程 在遍历 就会出现 java.util.ConcurrentModificationException
         * 但是如果不加 CONCURRENT 就不会出现这个异常 因为如果加了 那么这个收集器的结果容器就会变成一个 那么这是就有可能出现 一个线程向结果容器中添加元素
         * 别一个线程在遍历结果容器
         * 不加的话一个线程一个结果容器 这样就永远不会出现 这种异常的情况 我们可以从结果容器中也可以看出
         *
         *
         * 加CONCURRENT:
         *  加CONCURRENT的话他的结果容器都很长 说明所用线程使用的是同一个结果容器
         *  加CONCURRENT combiner() 方法就不会执行 因为只有一个结果容器 所以就不需要 调用 combiner() 方法 来合并结果容器
         *  加CONCURRENT supplier() 方法就会执行一次 所以只有一个结果容器
         *
         * 打印的结果:
         * MySetCollector.characteristics
         * MySetCollector.supplier
         * ----------supplier.lambda执行了-----------
         * MySetCollector.accumulator
         * result: [] accumulator.for: main
         * result: [p] accumulator.for: main
         * result: [p] accumulator.for: ForkJoinPool.commonPool-worker-2
         * result: [p, 1] accumulator.for: main
         * result: [p, 1, welcome] accumulator.for: ForkJoinPool.commonPool-worker-2
         * result: [p, 1, as, welcome] accumulator.for: ForkJoinPool.commonPool-worker-4
         * result: [p, 1, as, welcome] accumulator.for: ForkJoinPool.commonPool-worker-9
         * result: [p, 1, as, hello, l, welcome] accumulator.for: ForkJoinPool.commonPool-worker-11
         * result: [p, 1, as, welcome] accumulator.for: main
         * result: [p, 1, as, f, j, hello, l, welcome] accumulator.for: ForkJoinPool.commonPool-worker-13
         * result: [p, 1, as, 2, f, j, hello, l, welcome] accumulator.for: main
         * result: [p, 1, a, as, 2, world, f, j, hello, l, welcome] accumulator.for: main
         * result: [p, 1, as, f, j, hello, l, welcome] accumulator.for: ForkJoinPool.commonPool-worker-11
         * result: [p, 1, as, j, hello, l, welcome] accumulator.for: ForkJoinPool.commonPool-worker-9
         * MySetCollector.characteristics
         * MySetCollector.finisher
         * {a=a, f=f, g=g, j=j, k=k, l=l, p=p, 1=1, as=as, 2=2, world=world, s=s, hello=hello, welcome=welcome}
         *  =++++++++++++++++++++++++
         *  MySetCollector.characteristics
         *  MySetCollector.finisher
         *  ------------------分割线----------------------
         *
         * 不加CONCURRENT:
         *  不加CONCURRENT的话他的结果容器都很短 说明所用每个线程使用都有自己的结果容器
         *  不加CONCURRENT combiner() 方法就会执行 因为一个线程一个结果容器 所以需要调用 combiner() 方法来合并结果容器
         *  不加CONCURRENT supplier() 方法会多次执行 所以拥有多个结果容器
         *
         * 打印的结果
         * MySetCollector.characteristics
         * MySetCollector.supplier
         * MySetCollector.accumulator
         * MySetCollector.combiner
         * MySetCollector.characteristics
         * ----------supplier.lambda执行了-----------
         * ----------supplier.lambda执行了-----------
         * ----------supplier.lambda执行了-----------
         * ----------supplier.lambda执行了-----------
         * ----------supplier.lambda执行了-----------
         * ----------supplier.lambda执行了-----------
         * ----------supplier.lambda执行了-----------
         * ----------supplier.lambda执行了-----------
         * result: [] accumulator.for: ForkJoinPool.commonPool-worker-11
         * result: [] accumulator.for: ForkJoinPool.commonPool-worker-13
         * result: [] accumulator.for: ForkJoinPool.commonPool-worker-6
         * result: [] accumulator.for: main
         * result: [] accumulator.for: ForkJoinPool.commonPool-worker-2
         * result: [] accumulator.for: ForkJoinPool.commonPool-worker-4
         * result: [p] accumulator.for: main
         * result: [f] accumulator.for: ForkJoinPool.commonPool-worker-11
         * result: [] accumulator.for: ForkJoinPool.commonPool-worker-9
         * set1 = [a]
         * result: [p, 1] accumulator.for: main
         * set1 = [hello]
         * result: [p, 1, as] accumulator.for: main
         * set2 = [f, g]
         * result: [j] accumulator.for: ForkJoinPool.commonPool-worker-9
         * result: [p, 1, as, 2] accumulator.for: main
         * set2 = [welcome]
         * result: [p, 1, as, 2, world] accumulator.for: main
         * set1 = [j, k]
         * set1 = [p, 1, as, 2, world, s]
         * set2 = []
         * set2 = [l]
         * set1 = [p, 1, as, 2, world, s]
         * set1 = [a, f, g]
         * set2 = [hello, welcome]
         * set2 = [j, k, l]
         * set1 = [a, f, g, j, k, l]
         * set2 = [p, 1, as, 2, world, s, hello, welcome]
         * MySetCollector.characteristics
         * MySetCollector.finisher
         * {a=a, f=f, g=g, j=j, k=k, l=l, p=p, 1=1, as=as, 2=2, world=world, s=s, hello=hello, welcome=welcome}
         *  =++++++++++++++++++++++++
         * ------------------分割线----------------------
         *
         * 使用parallelStream(并发流) 他的初始线程数 默认是当前CPU 的超线程数
         * 查看方法
         * int i = Runtime.getRuntime().availableProcessors();
         *
         * 修改parallelStream(并发流)初始的线程数  System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism", "64");
         *
         * 注意:
         * 1.parallelStream线程不安全问题(加锁、使用线程安全的集合或者集合采用collect()或reduce()操作就是满足线程安全的;
         * 2.parallelStream 适用的场景是CPU密集型的,假如本身电脑CPU的负载很大,那还到处用并行流,那并不能起到作用,切记不要再paralelSreram操作是中使用IO流;
         * 3.不要在多线程中使用parallelStream,原因同上类似,大家都抢着CPU是没有提升效果,反而还会加大线程切换开销;
         * 4. 初始的线程数一般不建议修改
         */
        int i = Runtime.getRuntime().availableProcessors();
        System.out.println("i = " + i);
        Map<String, String> collect = set.parallelStream().collect(new MySetToMapCollector<>());
        ///这种写法等价于上面的写法    collect = set.stream().parallel().collect(new MySetToMapCollector<>());
        // collect = set.stream().sequential().collect(new MySetToMapCollector<>()); 这种写法等价串行 collect = set.stream().collect(new MySetToMapCollector<>());
        System.out.println(collect);
        System.out.println(" =++++++++++++++++++++++++ ");
    }


猜你喜欢

转载自blog.csdn.net/weixin_43939924/article/details/111407649
今日推荐