Java 流(Stream)处理操作完成之后,我们可以收集这个流中的元素,使之汇聚成一个最终结果。这个结果可以是一个对象,也可以是一个集合,甚至可以是一个基本类型数据。

以记录 Record 为例:

@Data
@NoArgsConstructor
@AllArgsConstructor
public static class Record {
private String col1;
private String col2;
private int col3;
}

记录 Record 包含三个属性:列1(col1)、列2(col2)和 列3(col3)。

创建四个记录实例:

Record r1 = new Record("a", "1", 1);
Record r2 = new Record("a", "2", 2);
Record r3 = new Record("b", "3", 3);
Record r4 = new Record("c", "4", 4);

添加到列表:

List<Record> records = new ArrayList<>();

records.add(r1);
records.add(r2);
records.add(r3);
records.add(r4);

收集所有记录的 列1 值,以列表形式存储结果

List<String> col1List = records.stream()
.map(Record::getCol1)
.collect(Collectors.toList());
log.info("col1List: {}", Json.toJson(col1List));

输出结果:

col1List: ["a","a","b","c"]

收集所有记录的 列1 值,且去重,以集合形式存储

Set<String> col1Set = records.stream()
.map(Record::getCol1)
.collect(Collectors.toSet());
log.info("col1Set: {}", Json.toJson(col1Set));

输出结果:

col1Set: ["a","b","c"]

收集记录的 列2 值和 列3 值的对应关系,以字典形式存储

Map<String, Integer> col2Map = records.stream()
.collect(Collectors.toMap(Record::getCol2, Record::getCol3));
log.info("col2Map: {}", Json.toJson(col2Map));

输出结果:

col2Map: {"1":1,"2":2,"3":3,"4":4}

记录的 列2 不能有重复值,否则会抛出 Duplicate key 异常。


收集所有记录中 列3 值最大的记录

Record max = records.stream()
.collect(Collectors.maxBy(Comparator.comparing(Record::getCol3)))
.orElse(null);
log.info("max: {}", Json.toJson(max));

输出结果:

max: {"col1":"c","col2":"4","col3":4}

收集所有记录中 列3 值的总和

int sum = records.stream()
.collect(Collectors.summingInt(Record::getCol3));
log.info("sum: {}", sum);

输出结果:

sum: 10

流的收集需要通过 Stream.collect() 方法完成,方法的参数是一个 Collector(收集器);收集结果时,需要根据收集结果的目标类型,传递特定的收集器实例,如上:

  • Collectors.toList()
  • Collectors.toSet()
  • Collectors.toMap()
  • Collectors.maxBy()
  • Collectors.summingInt()

Collectors(java.util.stream.Collectors) 是一个工具类,内置若干收集器,我们可以通过调用不同的方法快速获取相应的收集器实例。

收集器(java.util.stream.Collector)本质是一个 接口,包含以下五个方法:

Collectors.toList() 为例演示收集器的工作过程。


创建一个中间结果容器

supplier() 方法会返回一个 Supplier 实例,调用该实例的 get() 方法,会创建一个中间结果容器。

@Override
public Supplier<List<String>> supplier() {
return new Supplier<List<String>>() {
@Override
public List<String> get() {
List<String> container = new ArrayList<>(); return container;
}
};
}

考虑到收集的元素类型 String,这里的中间结果容器类型为 ArrayList。

根据收集过程的需要,中间结果容器可以是任意的数据结构。


逐一遍历流中的每个元素,处理完成之后,添加到中间结果

accumulator() 方法会返回一个 BiConsumer 实例,它有一个 accept() 方法,

参数1:中间结果

参数2:流中遍历到的某个元素

遍历过程是 Java 自动完成的,每遍历一个元素,会自动调用 BiConsumer.accept 方法。我们只需要在方法中实现元素的处理过程,然后把元素的处理结果添加到中间结果中就可以了。

@Override
public BiConsumer<List<String>, String> accumulator() {
return new BiConsumer<List<String>, String>() {
@Override
public void accept(List<String> container, String col) {
container.add(col);
}
};
}

这个示例中,流中的元素不需要任何处理,直接添加至中间结果即可。


中间结果转换成最终结果

finisher() 方法会返回一个 Fuction 实例,它有一个 apply() 方法,

参数:中间结果

返回:最终结果

遍历过程结束之后,Java 会自动调用 Function.apply() 方法,将中间结果转换成最终结果。

@Override
public Function<List<String>, List<String>> finisher() {
return new Function<List<String>, List<String>>() {
@Override
public List<String> apply(List<String> container) {
return container;
}
};
}

这个示例中,中间结果就是最终结果,不需要任何处理,直接返回中间结果即可。


combiner()是做什么的?

流中的元素可以被并行处理,这样的流称为并行流。并行流相当于把一个大流切分成多个小流,内部使用多线程,并行处理这些小流。每一个小流遍历完成之后,都会产生一个小的中间结果,需要将这些小的中间结果合并成一个大的中间结果。

假设有两个小流,收集开始时,会创建两个中间结果:

中间结果也是通过 Supplier.get() 方法创建的。

并行遍历两个小流,将各自流的处理结果添加到各自的中间结果中:

combiner() 方法会返回一个 BinaryOperator 实例,它有一个 apply() 方法:

参数1:中间结果1

参数2:中间结果2

返回:中间结果

Java 会在合适的时机自动调用 BinaryOperator.apply() 方法,将小的中间结果合并成大的中间结果。

@Override
public BinaryOperator<List<String>> combiner() {
return new BinaryOperator<List<String>>() {
@Override
public List<String> apply(List<String> container1, List<String> container2) {
container1.addAll(container2);
return container1;
}
};
}

characteristics()是什么的?

characteristics() 会返回一个 Characteristics(枚举)集合实例,用于设定收集器的特性,支持以下三个值:

  • CONCURRENT

    收集器支持并发使用

  • UNORDERED

    收集器不保证元素顺序

  • IDENTITY_FINISH

    收集器中间结果可直接转换成最终结果

Java 可以根据这些特性值,保证收集器正确地、有效率地执行。


完整代码

Collector<String, List<String>, List<String>> collector = new Collector<String, List<String>, List<String>>() {
@Override
public Supplier<List<String>> supplier() {
return new Supplier<List<String>>() {
@Override
public List<String> get() {
List<String> container = new ArrayList<>(); return container;
}
};
} @Override
public BiConsumer<List<String>, String> accumulator() {
return new BiConsumer<List<String>, String>() {
@Override
public void accept(List<String> container, String col) {
container.add(col);
}
};
} @Override
public BinaryOperator<List<String>> combiner() {
return new BinaryOperator<List<String>>() {
@Override
public List<String> apply(List<String> container1, List<String> container2) {
container1.addAll(container2);
return container1;
}
};
} @Override
public Function<List<String>, List<String>> finisher() {
return new Function<List<String>, List<String>>() {
@Override
public List<String> apply(List<String> container) {
return container;
}
};
} @Override
public Set<Characteristics> characteristics() {
return new HashSet<>();
}
}; col1List = records.stream()
.map(Record::getCol1)
.collect(collector);
log.info("col1List: {}", Json.toJson(col1List));

Java 流处理之收集器的更多相关文章

  1. JAVA流式布局管理器--JAVA基础

    JAVA流式布局管理器的使用: FlowLayoutDeme.java: import java.awt.*;import javax.swing.*;public class FlowLayoutD ...

  2. JAVA 流式布局管理器

    //流式布局管理器 import java.awt.*; import javax.swing.*; public class Jiemian2 extends JFrame{ //定义组件 JBut ...

  3. 深入理解Java虚拟机03--垃圾收集器与内存分配策略

    一.概述  哪些内存需要回收? 什么时候回收? 如何回收? 二.对象已死吗  1.引用计数算法  定义:给对象添加一个引用计数器,当增加一个引用时,加1,当一个引用时,减1; 缺陷:当对象之间互相循环 ...

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

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

  5. JAVA8给我带了什么——流的概念和收集器

    到现在为止,笔者不敢给流下定义,从概念来讲他应该也是一种数据元素才是.可是在我们前面的代码例子中我们可以看到他更多的好像在表示他是一组处理数据的行为组合.这让笔者很难去理解他的定义.所以笔者不表态.各 ...

  6. Stream01 定义、迭代、操作、惰性求值、创建流、并行流、收集器、stream运行机制

    1 Stream Stream 是 Java 8 提供的一系列对可迭代元素处理的优化方案,使用 Stream 可以大大减少代码量,提高代码的可读性并且使代码更易并行. 2 迭代 2.1 需求 随机创建 ...

  7. Java GC收集器配置说明

    根据Java GC收集器具体分类,我们可以看出JVM根据需求不同提供了三种选择:串行收集器.并行收集器.并发收集器. 串行收集器只适用于小数据量的情况,我们主要了解一下并行收集器和并发收集器.默认情况 ...

  8. JAVA G1收集器 第11节

    JAVA G1收集器 第11节 上两章我们讲了新生代和年老代的收集器,那么这一章的话我们就要讲一个收集范围涵盖整个堆的收集器——G1收集器. 先讲讲G1收集器的特点,他也是个多线程的收集器,能够充分利 ...

  9. JAVA 年老代收集器 第10节

    JAVA 年老代收集器 第10节 上一章我们讲了新生代的收集器,那么这一章我们要讲的就是关于老年代的一些收集器.老年代的存活的一般是大对象以及生命很顽强的对象,因此新生代的复制算法很明显不能适应该区域 ...

随机推荐

  1. bat-使用bat安装jdk和配置环境变量

    文件路径 @echo off Setlocal enabledelayedexpansion @REM vscode中自动开启延迟环境变量扩展, %~d0 cd %~dp0 @REM dir echo ...

  2. Hdfs存储策略

    一.磁盘选择策略 1.1.介绍 在HDFS中,所有的数据都是存在各个DataNode上的.而这些DataNode上的数据都是存放于节点机器上的各个目录中的,而一般每个目录我们会对应到1个独立的盘,以便 ...

  3. 写for循环快捷生成方式

    在编写代码过程常常会使用到for循环,这里给大家介绍一种快捷写法len.fori+回车键(其中len为循环长度)举个栗子,编写10次循环 8.fori

  4. Markdown第一次学习

    # # Markdown学习 一级标题: #空格+标题名称+回车得到一级标题 ## 二级标题 一级标题方法中变成两个##号 ### 三级标题 变成三个###号,以此类推,最多到六级标题 ## 字体 h ...

  5. 抓包整理外篇——————autoResponder、composer 、statistics [ 三]

    前言 经过了前文的介绍的部分已经能够为自己抓包提供一个舒适的环境了,但是舒服的拿到我们的包后,可能有些需求还是难以搞定,fiddler 提供了我们一些其他模块,让我们工作轻松,请往下看. 正文 aut ...

  6. 链表设计与Java实现,手写LinkedList这也太清楚了吧!!!

    链表设计与实现 在谈链表之前,我们先谈谈我们平常编程会遇到的很常见的一个问题.如果在编程的时候,某个变量在后续编程中仍需使用,我们可以用一个局部变量来保存该值,除此之外一个更加常用的方法就是使用容器了 ...

  7. day01 Java_JVM,JCR,JDK

    精华笔记: java开发环境: 编译运行过程: 编译期:.java源文件,经过编译,生成.class字节码文件 运行期:JVM加载.class并运行.class(0和1) 特点:跨平台.一次编程到处使 ...

  8. dos命令-*02

    1.cmd状态下 tab快速补充文件名 ↑↓快速填充之前输入的文件名 2.//这是Java的快速入门 //对代码的相关说明 //1.public class Hello 表示Hello是一个类,是一个 ...

  9. ELK 日志分析系统的部署

    一.ELK简介 ElasticSearch介绍Elasticsearch是一个基于Lucene的搜索服务器. 它提供了一个分布式多用户能力的全文搜索引擎,基于RESTful web接口. Elasti ...

  10. mysql面试题整理

    1 myisam 和 innodb 引擎的区别 innodb 支持事务,外键,myisam 不支持 innodb 支持 mvcc ,myisam 不支持 innodb 支持表锁.行锁,myisam 仅 ...