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方法中必须做正确的处理,否则肯定抛出类型转换的异常。
Java8中重要的收集器Collector的更多相关文章
- [四] java8 函数式编程 收集器浅析 收集器Collector常用方法 运行原理 内部实现
Collector常见用法 常用形式为: .collect(Collectors.toList()) collect()是Stream的方法 Collectors 是收集器Collect ...
- java8学习之自定义收集器深度剖析与并行流陷阱
自定义收集器深度剖析: 在上次[http://www.cnblogs.com/webor2006/p/8342427.html]中咱们自定义了一个收集器,这对如何使用收集器Collector是极有帮助 ...
- java8学习之自定义收集器实现
在上次花了几个篇幅对Collector收集器的javadoc进行了详细的解读,其涉及到的文章有: http://www.cnblogs.com/webor2006/p/8311074.html htt ...
- Java8学习笔记(十)--自定义收集器
前言 以前写过Java8中的自定义收集器,当时只是在文章末尾放了个例子,觉得基本用法挺简单,而且有些东西没搞懂(比如combiner方法到底做什么的),没有专门写,过了一段时间又忘了,所以,即使还是没 ...
- [一] java8 函数式编程入门 什么是函数式编程 函数接口概念 流和收集器基本概念
本文是针对于java8引入函数式编程概念以及stream流相关的一些简单介绍 什么是函数式编程? java程序员第一反应可能会理解成类的成员方法一类的东西 此处并不是这个含义,更接近是数学上的 ...
- 学习JVM--垃圾回收(二)GC收集器
1. 前言 在上一篇文章中,介绍了JVM中垃圾回收的原理和算法.介绍了通过引用计数和对象可达性分析的算法来筛选出已经没有使用的对象,然后介绍了垃圾收集器中使用的三种收集算法:标记-清除.标记-整理.标 ...
- 学习JVM-GC收集器
1. 前言 在上一篇文章中,介绍了JVM中垃圾回收的原理和算法.介绍了通过引用计数和对象可达性分析的算法来筛选出已经没有使用的对象,然后介绍了垃圾收集器中使用的三种收集算法:标记-清除.标记-整理.标 ...
- JDK1.7 Update14 HotSpot虚拟机GC收集器
在测试服务器上使用如下命令可以查看当前使用的 GC收集器,当然不止这一个命令可以看到,还有其他一些方式 第三列”=”表示第四列是参数的默认值,而”:=” 表明了参数被用户或者JVM赋值了 [csii@ ...
- 垃圾收集器之:CMS收集器
HotSpot JVM的并发标记清理收集器(CMS收集器)的主要目标就是:低应用停顿时间.该目标对于大多数交互式应用很重要,比如web应用.在我们看一下有关JVM的参数之前,让我们简要回顾CMS收集器 ...
随机推荐
- 卷积的三种模式:full、same、valid + 卷积输出size的计算
转自https://blog.csdn.net/u012370185/article/details/95238828 通常用外部api进行卷积的时候,会面临mode选择. 这三种mode的不同点:对 ...
- 14 count(*)
14 count(*) count(*)实现方式 首先要声明,在不同的mysql引擎中,count(*)有不同的实现方式. --myisam引擎把一个表的总行数存在了磁盘,因此执行count(*)的时 ...
- beego 如何自定error
beego通过Redirect方法来进行跳转: 1 2 3 func (this *AddController) Get() { this.Redirect("/", 30 ...
- Linux CentOS安装搭建FTP文件服务
本文环境:centos7,IP=192.168.1.11 1.安装vsftpd和默认配置启动 1.1 安装vsftpd yum install -y vsftpd 1.2 启动vsftpd syste ...
- Flash-aware Page Replacement Algorithm
1.Abstract:(1)字体太乱,单词中有空格(2) FAPRA此名词第一出现时应有“ FAPRA(Flash-aware Page Replacement Algorithm)”说明. 2.in ...
- lambda常用方法
一:forEach() 循环遍历 List<Integer> costBeforeTax = Arrays.asList(100, 200, 300, 400, 500); costBe ...
- 瀑布布局(waterflall flow)实现
瀑布流,又称瀑布流式布局.是比较流行的一种网站页面布局,视觉表现为参差不齐的多栏布局,随着页面滚动条向下滚动.这种布局还会不断加载数据块并附加至当前尾部.最早采用此布局的网站是Pinterest,逐渐 ...
- Mybatis数据基本操作
<insert id="doCreate" parameterType="News"><!--添加数据--> INSERT INTO n ...
- List是有序的Set是无序的吗? List和Set对比
import java.util.*; /* * List和Set对比 * */ public class ListVSSet { public static void main(String[] a ...
- .Net Core 3.0使用Grpc进行远程过程调用
因为.Net Core3.0已经把Grpc作为一等臣民了,作为爱好新技术的我,当然要尝鲜体验一下了,当然感觉是Grpc作为跨语言的产品做的相当好喽,比起Dubbo这种的,优势和劣势还是比较明显的. 我 ...