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方法中必须做正确的处理,否则肯定抛出类型转换的异常。

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

  1. [四] java8 函数式编程 收集器浅析 收集器Collector常用方法 运行原理 内部实现

    Collector常见用法     常用形式为:   .collect(Collectors.toList()) collect()是Stream的方法 Collectors  是收集器Collect ...

  2. java8学习之自定义收集器深度剖析与并行流陷阱

    自定义收集器深度剖析: 在上次[http://www.cnblogs.com/webor2006/p/8342427.html]中咱们自定义了一个收集器,这对如何使用收集器Collector是极有帮助 ...

  3. java8学习之自定义收集器实现

    在上次花了几个篇幅对Collector收集器的javadoc进行了详细的解读,其涉及到的文章有: http://www.cnblogs.com/webor2006/p/8311074.html htt ...

  4. Java8学习笔记(十)--自定义收集器

    前言 以前写过Java8中的自定义收集器,当时只是在文章末尾放了个例子,觉得基本用法挺简单,而且有些东西没搞懂(比如combiner方法到底做什么的),没有专门写,过了一段时间又忘了,所以,即使还是没 ...

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

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

  6. 学习JVM--垃圾回收(二)GC收集器

    1. 前言 在上一篇文章中,介绍了JVM中垃圾回收的原理和算法.介绍了通过引用计数和对象可达性分析的算法来筛选出已经没有使用的对象,然后介绍了垃圾收集器中使用的三种收集算法:标记-清除.标记-整理.标 ...

  7. 学习JVM-GC收集器

    1. 前言 在上一篇文章中,介绍了JVM中垃圾回收的原理和算法.介绍了通过引用计数和对象可达性分析的算法来筛选出已经没有使用的对象,然后介绍了垃圾收集器中使用的三种收集算法:标记-清除.标记-整理.标 ...

  8. JDK1.7 Update14 HotSpot虚拟机GC收集器

    在测试服务器上使用如下命令可以查看当前使用的 GC收集器,当然不止这一个命令可以看到,还有其他一些方式 第三列”=”表示第四列是参数的默认值,而”:=” 表明了参数被用户或者JVM赋值了 [csii@ ...

  9. 垃圾收集器之:CMS收集器

    HotSpot JVM的并发标记清理收集器(CMS收集器)的主要目标就是:低应用停顿时间.该目标对于大多数交互式应用很重要,比如web应用.在我们看一下有关JVM的参数之前,让我们简要回顾CMS收集器 ...

随机推荐

  1. js继承的实现(原型/链、函数伪装)

    一.原型继承父类的实例 //父类及其原型属性/方法 function SuperType () { this.name = ['zc','ls','ww']; } SuperType.prototyp ...

  2. java:Linux(简单命令,远程ssh使用hostname访问,.免密钥登录配置)

    1.临时关闭防火墙: service  iptables  stop 临时开启防火墙: service  iptables  start 查看防火墙状态: service  iptables  sta ...

  3. 基于OpenCV的三维数据点的曲面重构_MySurefaceReconstruction

    在Opencv中有个Viz模块,可以显示三维物体,还可以实现三维动画,本来是很好的东东,但是里面的函数.类的说明太过简单,始终不得要领.不过其中一个扩展功能非常好,就是你可以在vtk中设计自己的模型类 ...

  4. OpenStack组件——cinder存储服务

    1.cinder 介绍 1)理解 Block Storage 操作系统获得存储空间的方式一般有两种: (1)通过某种协议(SAS,SCSI,SAN,iSCSI 等)挂接裸硬盘,然后分区.格式化.创建文 ...

  5. webdriervAPI(WebElement接口常用方法)

    from  selenium  import  webdriver driver  =  webdriver.Chorme() driver.get("http://www.baidu.co ...

  6. Windows系统中CMD wmic查看硬盘、内存、CPU、BIOS、网卡等信息。(附带脚本)

    目录 Windows系统中CMD wmic查看硬盘.内存.CPU.BIOS.网卡等信息. 查看内存相关 查看内存主板数量(两条命令均可) 查看物理内存 查看逻辑内存 查看缓存 查看虚拟内存 查看网络相 ...

  7. [Python3 练习] 011 利用异常解题

    目录 函数式编程 Functional Programming 1. 简介 2. 函数 3. 匿名函数 3.1 lambda 表达式也称"匿名函数" 3.2 lambda 表达式的 ...

  8. 关于一个查询的JAVA界面,希望对你有启发

    package work2; import java.awt.BorderLayout; import javax.swing.JButton; import javax.swing.JFrame; ...

  9. acmsguru

    acmsguru 101 - Domino 要求每两个相邻的多尼诺骨牌相对的数字相同,即做一个一笔画 #include<bits/stdc++.h> using namespace std ...

  10. CSP 俄罗斯方块(201604-2)

    问题描述 俄罗斯方块是俄罗斯人阿列克谢·帕基特诺夫发明的一款休闲游戏. 游戏在一个15行10列的方格图上进行,方格图上的每一个格子可能已经放置了方块,或者没有放置方块.每一轮,都会有一个新的由4个小方 ...