Java8中重要的收集器Collector

Collector介绍

Java8的stream api能很方便我们对数据进行统计分类等工作,函数式编程的风格让我们方便并且直观地编写统计代码。

例如:

Stream<Integer> stream = Stream.iterate(1, item -> item+2).limit(6);
//        stream.filter(item -> item > 2).mapToInt(item -> item * 2).skip(2).limit(2).sum();//.reduce(0, (val, val2)->val+val2);
//        System.out.println(sum);
IntSummaryStatistics summaryStatistics = stream.filter(item -> item > 2).mapToInt(item -> item * 2).skip(2).summaryStatistics();
System.out.println(summaryStatistics.getAverage());

stream里有一个collect(Collector c)方法,这个方法里面接收一个Collector的实例。这里我们要弄清楚Collector与Collectors之间的关系。

作为collect方法的参数,Collector是一个接口,它是一个可变的汇聚操作,将输入元素累计到一个可变的结果容器中;它会在所有元素都处理完毕后,将累积的结果转换为一个最终的表示(这是一个可选操作);

这些如果你不太懂,请继续往下看,结合下面自定义Collector,相信你可以理解这些内容。

Collectors本身提供了关于Collector的常见汇聚实现,Collectors的内部类CollectorImpl实现了Collector接口,Collectors本身实际上是一个工厂。

Collector的使用

很多时候我们会用到Collectors的toList方法,Collectors中提供了将流中元素累积到汇聚结果的各种方式,例如Counting、Joining、maxBy等等。下面是一个例子:

//分组
        List<Student> list1 = Arrays.asList(s1, s2, s3, s4, s5);
        //根据名称分组
//        Map<String, List<Student>> map = list1.stream().collect(Collectors.groupingBy(Student::getName));
        //先根据名称分组,然后求每组平均分
        Map<String, Double> map  = list1.stream().collect(Collectors.groupingBy(Student::getName, Collectors.averagingDouble(Student::getScore)));
        System.out.println(map);

        //分区
        Map<Boolean, List<Student>> map1 = list1.stream().collect(Collectors.partitioningBy(item -> item.getScore() >= 90));
        System.out.println(map1);

        System.out.println("------2-----");

        //先根据名称分组再根据分数分组
        Map<String, Map<Integer, List<Student>>> map2 = list1.stream().collect(Collectors.groupingBy(Student::getName, Collectors.groupingBy(Student::getScore)));
        System.out.println(map2);

这里分区分组与数据库中的分区分组的概念类似。

Collectors are designed to be composed; many of the methods in {@link Collectors} are functions that take a collector and produce a new collector.

附上javadoc上的一句话,这句话说明收集操作是可以嵌套的。

自定义Collector

前面讲过,Collectors本身提供了关于Collector的常见汇聚实现,那么程序员自身也可以根据情况定义自己的汇聚实现。

首先我们看下Collector接口的结构

public interface Collector<T, A, R> {
    Supplier<A> supplier();
    BiConsumer<A, T> accumulator();
    BinaryOperator<A> combiner();
    Function<A, R> finisher();

    Set<Characteristics> characteristics();
}

其中这里的泛型所表示的含义是:

T:表示流中每个元素的类型。

A:表示中间结果容器的类型。

R:表示最终返回的结果类型。

Collector中还定义了一个枚举类Characteristics,有三个枚举值,理解这三个值的含义对于我们自己编写正确的收集器也是至关重要的。

  • Characteristics.CONCURRENT:表示中间结果只有一个,即使在并行流的情况下。所以只有在并行流且收集器不具备CONCURRENT特性时,combiner方法返回的lambda表达式才会执行(中间结果容器只有一个就无需合并)。
  • Characteristics.UNORDER:表示流中的元素无序。
  • Characteristics.IDENTITY_FINISH:表示中间结果容器类型与最终结果类型一致,此时finiser方法不会被调用。

我们再来看一下Collectors中toList方法的实现。

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);
    }

static final Set<Collector.Characteristics> CH_ID
            = Collections.unmodifiableSet(EnumSet.of(Collector.Characteristics.IDENTITY_FINISH));

到此,不知你是否对自定义收集器有那么点感觉了?

那么到底怎么实现自定义收集器呢,下面举例子来看看。

public class MyCollectorImpl<T> implements Collector<T, Set<T>, Set<T>> {
    @Override
    public Supplier<Set<T>> supplier() {
        return HashSet<T>::new;
    }

    @Override
    public BiConsumer<Set<T>, T> accumulator() {
        return Set<T>::add;
    }

    @Override
    public BinaryOperator<Set<T>> combiner() {
        return (set, item) -> {set.addAll(item); return set;};
    }

    @Override
    public Function<Set<T>, Set<T>> finisher() {

        return Function.identity();
    }

    @Override
    public Set<Characteristics> characteristics() {
        return Collections.unmodifiableSet(EnumSet.of(IDENTITY_FINISH,UNORDERED));
    }

    public static void main(String[] args) {
        List<String> list = Arrays.asList("hello", "world", "welcome");
        Set<String> set = list.stream().collect(new MyCollectorImpl<>());
        set.forEach(System.out::println);
    }
}

这是一个简单的例子,我们给收集器加上了IDENTITY_FINISH特性,此时finisher方法返回的lambda表达式是不会得到调用的。这一点也可以从源码中得到验证。

例2:

public class MyCollectorImpl2<T> implements Collector<T, Set<T>, Map<T, T>>{
    @Override
    public Supplier<Set<T>> supplier() {
        return HashSet<T>::new;
    }

    @Override
    public BiConsumer<Set<T>, T> accumulator() {
        return Set<T>::add;
    }

    @Override
    public BinaryOperator<Set<T>> combiner() {
        return (set1, set2)->{
            set1.addAll(set2);
            return set1;
        };
    }

    @Override
    public Function<Set<T>, Map<T, T>> finisher() {
        return (set)->{
            HashMap<T, T> map = new HashMap();
            set.stream().forEach((item)->map.put(item, item));
            return map;
        };
    }

    @Override
    public Set<Characteristics> characteristics() {
        return Collections.unmodifiableSet(EnumSet.of(Characteristics.UNORDERED));
    }

    public static void main(String[] args) {
        List<String> list = Arrays.asList("hello", "world", "welcome");
        HashSet<String> set = new HashSet<>();
        set.addAll(list);

        Map<String, String> map = set.stream().collect(new MyCollectorImpl2<>());
        System.out.println(map);
    }
}

这个例子中由于中间累计结果容器的类型与最终类型不一致,在finisher方法中必须做正确的处理,否则肯定抛出类型转换的异常。

原文地址:https://www.cnblogs.com/zjm-1/p/9534675.html

时间: 2024-10-08 11:44:04

Java8中重要的收集器Collector的相关文章

java8函数式编程--收集器collector

java8的stream api能很方便我们对数据进行统计分类等工作,以前我们写的很多统计数据的代码往往是循环迭代得到的,不说别人看不懂,自己的代码放久了也要重新看一段时间才能看得懂.现在,java8吸收了适合科学计算的语言的新特性,提供了stream api,让我们方便并且直观地编写统计代码成为可能. stream里有一个collect(Collector c)方法,需要传入Collector收集器这个接口.现在就说说这个接口定义的职责. public interface Collector<

Java8中Stream的归约与收集

Java8中Stream的归约与收集 归约 reduce(T identity, BinaryOperator) / reduce(BinaryOperator) --可以将流中元素反复结合起来,得到一个值 public class Employee { private String name; private Integer age; private Double salary; private Status status; public Employee() { super(); } pub

2020了你还不会Java8新特性?(五)收集器比较器用法详解及源码剖析

收集器用法详解与多级分组和分区 为什么在collectors类中定义一个静态内部类? static class CollectorImpl<T, A, R> implements Collector<T, A, R> 设计上,本身就是一个辅助类,是一个工厂.作用是给开发者提供常见的收集器实现.提供的方法都是静态方法,可以直接调用. 函数式编程最大的特点:表示做什么,而不是如何做.开发者更注重如做什么,底层实现如何做. /** * Implementations of {@link

图解Java中的GC(分代收集器)

前面在Java垃圾收集算法中讲过垃圾收集算法中的分代收集器,今天看了一个视频发现里面将的也很不错,所以决定再总结一下. 我们知道,在分代收集算法中堆空间被分为新生代和老年代.因为新生代中对象的存活率比较低,所以一般采用复制算法,老年代的存活率一般比较高,一般使用"标记-清理"或者"标记-整理"算法进行回收. 上面的这个图已经很清楚的将堆的分区展现出来了. 下面我们来看看具体的算法过程. 新创建的对象一般放在新生代的Enden区,如下图所示. 上面对象中,绿色代表的是

NX二次开发-Block UI C++界面Face Collector(面收集器)控件的获取(持续补充 )

Face Collector(面收集器)控件的获取 1 NX9+VS2012 2 3 #include <uf.h> 4 #include <uf_obj.h> 5 6 7 UF_initialize(); 8 9 //面收集器控件 10 PropertyList* FaceSelectProps = face_select0->GetProperties(); 11 std::vector<NXOpen::TaggedObject *> faces = Face

深入JVM系列(二)之GC机制、收集器与GC调优(转)

一.回顾JVM内存分配 需要了解更多内存模式与内存分配的,请看 深入JVM系列(一)之内存模型与内存分配 1.1.内存分配: 1.对象优先在EDEN分配2.大对象直接进入老年代 3.长期存活的对象将进入老年代 4.适龄对象也可能进入老年代:动态对象年龄判断 动态对象年龄判断: 虚拟机并不总是要求对象的年龄必须达到MaxTenuringThreshold才能晋升到老年代,当Survivor空间的相同年龄的所有对象大小总和大于Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代

【5】JVM-垃圾收集器

通过学习了解到现在商用的JVM中的垃圾收集采用的是分代收集算法,即针对不同年代采用不同的收集算法.在JVM中,GC主要作用于堆内存中,堆内存又被划分为新生代和老年代,由于新生代对象绝大多数是朝生夕死,而老年代相对存活时间就很长,故而需要使用不同的垃圾收集机制,所以垃圾收集器也就分为新生代收集器和老年代收集器,两者相互组合进行JVM堆内存的空间回收(下图中相连的垃圾收集器表示可以相互组合,注意Serial Old和CMS也可以联合进行老年代的垃圾收集).JDK6u14中开始测试的G1垃圾收集器,正

Java8中聚合操作collect、reduce方法详解

Stream的基本概念 Stream和集合的区别: Stream不会自己存储元素.元素储存在底层集合或者根据需要产生.Stream操作符不会改变源对象.相反,它会返回一个持有结果的新的Stream.3.Stream操作可能是延迟执行的,这意味着它们会等到需要结果的时候才执行.Stream操作的基本过程,可以归结为3个部分: 创建一个Stream.在一个或者多个操作中,将指定的Stream转换为另一个Stream的中间操作.通过终止(terminal)方法来产生一个结果.该操作会强制它之前的延时操

JDK8-Java Streams 收集器

Collectors 类包含许多常见聚合操作的因素,比如累加到集合中.字符串串联.缩减和其他汇总计算,以及创建汇总表(通过groupingBy()). 内置收集器 收集器 行为 toList() 将元素收集到一个 List 中. toSet() 将元素收集到一个 Set 中. toCollection(Supplier<Collection>) 将元素收集到一个特定类型的 Collection 中. toMap(Function<T, K>, Function<T, V>