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>> {@Overridepublic Supplier<Set<T>> supplier() {return HashSet<T>::new;}@Overridepublic BiConsumer<Set<T>, T> accumulator() {return Set<T>::add;}@Overridepublic BinaryOperator<Set<T>> combiner() {return (set, item) -> {set.addAll(item); return set;};}@Overridepublic Function<Set<T>, Set<T>> finisher() {return Function.identity();}@Overridepublic 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>>{@Overridepublic Supplier<Set<T>> supplier() {return HashSet<T>::new;}@Overridepublic BiConsumer<Set<T>, T> accumulator() {return Set<T>::add;}@Overridepublic BinaryOperator<Set<T>> combiner() {return (set1, set2)->{set1.addAll(set2);return set1;};}@Overridepublic 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;};}@Overridepublic 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方法中必须做正确的处理,否则肯定抛出类型转换的异常。

Java8中重要的收集器Collector相关推荐

  1. java中collector使用_Java8中重要的收集器collector

    Collector介绍 Java8的stream api能很方便我们对数据进行统计分类等工作,函数式编程的风格让我们方便并且直观地编写统计代码. 例如: Stream stream = Stream. ...

  2. java收集器Collector

    一.收集器Collector //T:表示流中每个元素的类型. A:表示中间结果容器的类型. R:表示最终返回的结果类型. public interface Collector<T, A, R& ...

  3. Java8 Stream 自定义收集器Collector

    在之前的例子中,我们都是使用Collectors的静态方法提供的CollectorImpl,为接口Collector<T, A, R>的一个实现类,为了自定义我们自己的Collector, ...

  4. zipkin 自定义采样率_分组,采样和批处理– Java 8中的自定义收集器

    zipkin 自定义采样率 在第一篇文章的后续部分,这一次我们将编写一些更有用的自定义收集器:用于按给定的标准进行分组,采样输入,批量处理以及在固定大小的窗口上滑动. 分组(计数事件,直方图) 假设您 ...

  5. 分组,采样和批处理– Java 8中的自定义收集器

    在第一篇文章的后续部分,这次我们将编写一些更有用的自定义收集器:用于按给定的标准进行分组,采样输入,批处理和在固定大小的窗口上滑动. 分组(计数事件,直方图) 假设您有一些项目的集合,并且想要计算每个 ...

  6. JAVA8学习9-自定义收集器(Characteristics 使用说明)

    9 自定义收集器 ​ 在自定义收集器前,我们再确定下 Collector 接口函数接收参数和该实现的方法. public interface Collector<T, A, R> {// ...

  7. java中函数是什么_[一] java8 函数式编程入门 什么是函数式编程 函数接口概念 流和收集器基本概念...

    本文是针对于java8引入函数式编程概念以及stream流相关的一些简单介绍 什么是函数式编程? java程序员第一反应可能会理解成类的成员方法一类的东西 此处并不是这个含义,更接近是数学上的函数 看 ...

  8. [一] java8 函数式编程入门 什么是函数式编程 函数接口概念 流和收集器基本概念...

    本文是针对于java8引入函数式编程概念以及stream流相关的一些简单介绍 什么是函数式编程?   java程序员第一反应可能会理解成类的成员方法一类的东西 此处并不是这个含义,更接近是数学上的函数 ...

  9. Java8中Collector详解及自定义Collector

    文章目录 1.Collector介绍 2.Collector约束 3.Collector接口方法 4.理解Collector接口声明的方法 5.整合自定义Collector 6.使用collect方法 ...

最新文章

  1. AngularJs+bootstrap搭载前台框架——准备工作
  2. 关于遗留系统维护的讨论
  3. AI基础:Pandas简易入门
  4. 合并远程仓库到本地_git远程仓库
  5. LiveVideoStackCon深圳-服务端的任务越来越重
  6. 论文阅读笔记(五)【ACL 2021】Answering Ambiguous Questions through Generative Evidence Fusion and Round-Trip P
  7. 送10本今年最火的《动手学深度学习》
  8. 【Demo】HTML5 拍照上传
  9. 【斜率优化】[CEOI2004]锯木厂选址——从这里开始斜率优化的大门
  10. show,hide与fadeIn、fadeOu的区别
  11. ubuntu的系统服务管理
  12. 【原创】启动Win10自带的XPS/OXPS阅读器
  13. 高德地图 聚合效果
  14. 深入理解计算机系统-cachelab
  15. 谈谈我对上手MacOS的体验与macos常用快捷键总结
  16. SitePoint播客#87:MeltSheep和FireRock
  17. win7计算机广告更改,Win7电脑如何找到带来弹窗广告的软件【图文教程】
  18. 11月最新编程排行榜出炉,这个语言超过了C蝉联榜首~
  19. CentOS7下安装lnmp一键安装包
  20. Yade安装+优化教程

热门文章

  1. 基于51单片机+红外遥控解码+LCD1602显示
  2. Java学起来难吗?没基础能学懂吗?
  3. 一直以来伴随我的一些学习习惯(三):阅读方法
  4. 竞争分析 - Competitor Analysis
  5. C#+AE 栅格裁剪
  6. 浅谈POE供电系统中PSE两种供电方式——终端跨度、中间跨度
  7. xp系统锁定计算机快捷键,XP锁电脑的快捷键是哪个?
  8. 安卓手机摄像头维修流程
  9. opencv 等比例缩放图像(图像尺寸不变)
  10. Android App 性能优化之启动优化