Java 8 Stream 的终极技巧——Collectors 功能与操作方法详解

本文实例讲述了Java 8 Stream 的终极技巧——Collectors 功能与操作方法。分享给大家供大家参考,具体如下:

1. 前言

昨天在 Collection移除元素操作 相关的文章中提到了 Collectors 。相信很多同学对这个比较感兴趣,那我们今天就来研究一下 Collectors

2. Collectors 的作用

CollectorsJava 8 加入的操作类,位于 java.util.stream 包下。它会根据不同的策略将元素收集归纳起来,比如最简单常用的是将元素装入MapSetList 等可变容器中。特别对于 Java 8 Stream Api 来说非常有用。它提供了collect() 方法来对 Stream 流进行终结操作派生出基于各种策略的结果集。我们就借助于 Stream 来熟悉一下 Collectors 吧。我们依然用昨天的例子:

  List<String> servers = new ArrayList<>();
    servers.add("Felordcn");
    servers.add("Tomcat");
    servers.add("Jetty");
    servers.add("Undertow");
    servers.add("Resin");

3. Java 8 中 Collectors 的方法

Collectors 提供了一系列的静态方法供我们使用,通常情况我们静态导入即可使用。接下来我们来看看都提供了哪些方法吧。

3.1 类型归纳

这是一个系列,作用是将元素分别归纳进可变容器 ListMapSetCollection 或者ConcurrentMap

  Collectors.toList();
  Collectors.toMap();
  Collectors.toSet();
  Collectors.toCollection();
  Collectors.toConcurrentMap();

我们可以根据以上提供的 API 使用 Streamcollect 方法中的转换为熟悉的集合容器。非常简单这里不再演示。

3.2 joining

将元素以某种规则连接起来。该方法有三种重载 joining(CharSequence delimiter)joining(CharSequence delimiter,CharSequence prefix,CharSequence suffix)

 //  输出 FelordcnTomcatJettyUndertowResin
 servers.stream().collect(Collectors.joining());

 //  输出 Felordcn,Tomcat,Jetty,Undertow,Resin
 servers.stream().collect(Collectors.joining("," ));

 //  输出 [Felordcn,Tomcat,Jetty,Undertow,Resin]
 servers.stream().collect(Collectors.joining(",", "[", "]")); 

用的比较多的是读取 HttpServletRequest 中的 body

 HttpServletRequest.getReader().lines().collect(Collectors.joining());

3.3 collectingAndThen

该方法先执行了一个归纳操作,然后再对归纳的结果进行 Function 函数处理输出一个新的结果。

 // 比如我们将servers joining 然后转成大写,结果为: FELORDCN,TOMCAT,JETTY,UNDERTOW,RESIN
 servers.stream.collect(Collectors.collectingAndThen(Collectors.joining(","), String::toUpperCase));

3.4 groupingBy

按照条件对元素进行分组,和 SQL 中的 group by 用法有异曲同工之妙,通常也建议使用 Java 进行分组处理以减轻数据库压力。groupingBy 也有三个重载方法
我们将 servers 按照长度进行分组:

// 按照字符串长度进行分组  符合条件的元素将组成一个 List 映射到以条件长度为key 的 Map<Integer, List<String>> 中
servers.stream.collect(Collectors.groupingBy(String::length))

如果我不想 MapvalueList 怎么办? 上面的实现实际上调用了下面的方式:

 //Map<Integer, Set<String>>
 servers.stream.collect(Collectors.groupingBy(String::length, Collectors.toSet()))

我要考虑同步安全问题怎么办? 当然使用线程安全的同步容器啊,那前两种都用不成了吧! 别急! 我们来推断一下,其实第二种等同于下面的写法:

 Supplier<Map<Integer,Set<String>>> mapSupplier = HashMap::new;
 Map<Integer,Set<String>> collect = servers.stream.collect(Collectors.groupingBy(String::length, mapSupplier, Collectors.toSet()));

这就非常好办了,我们提供一个同步 Map 不就行了,于是问题解决了:

 Supplier<Map<Integer, Set<String>>> mapSupplier = () -> Collections.synchronizedMap(new HashMap<>());
 Map<Integer, Set<String>> collect = servers.stream.collect(Collectors.groupingBy(String::length, mapSupplier, Collectors.toSet()));

其实同步安全问题 Collectors 的另一个方法 groupingByConcurrent 给我们提供了解决方案。用法和 groupingBy 差不多。

3.5 partitioningBy

partitioningBy 我们在本文开头的提到的文章中已经见识过了,可以看作 groupingBy 的一个特例,基于断言(Predicate)策略分组。这里不再举例说明。

3.6 counting

该方法归纳元素的的数量,非常简单,不再举例说明。

3.7 maxBy/minBy

这两个方法分别提供了查找大小元素的操作,它们基于比较器接口 Comparator 来比较 ,返回的是一个 Optional 对象。 我们来获取 servers 中最小长度的元素:

 // Jetty
Optional<String> min = servers.stream.collect(Collectors.minBy(Comparator.comparingInt(String::length)));

这里其实 Resin 长度也是最小,这里遵循了 "先入为主" 的原则 。当然 Stream.min() 可以很方便的获取最小长度的元素。maxBy 同样的道理。

3.8 summingInt/Double/Long

用来做累加计算。计算元素某个属性的总和,类似 Mysqlsum 函数,比如计算各个项目的盈利总和、计算本月的全部工资总和等等。我们这里就计算一下 servers 中字符串的长度之和 (为了举例不考虑其它写法)。

 // 总长度 32
 servers.stream.collect(Collectors.summingInt(s -> s.length()));

3.9 summarizingInt/Double/Long

如果我们对 3.6章节-3.8章节 的操作结果都要怎么办?难不成我们搞5个 Stream 流吗? 所以就有了 summarizingIntsummarizingDoublesummarizingLong 三个方法。
这三个方法通过对元素某个属性的提取,会返回对元素该属性的统计数据对象,分别对应 IntSummaryStatisticsDoubleSummaryStatisticsLongSummaryStatistics。我们对 servers 中元素的长度进行统计:

 DoubleSummaryStatistics doubleSummaryStatistics = servers.stream.collect(Collectors.summarizingDouble(String::length));
 // {count=5, sum=32.000000, min=5.000000, average=6.400000, max=8.000000}
 System.out.println("doubleSummaryStatistics.toString() = " + doubleSummaryStatistics.toString());

结果 DoubleSummaryStatistics 中包含了 总数,总和,最小值,最大值,平均值 五个指标。

3.10 mapping

该方法是先对元素使用 Function 进行再加工操作,然后用另一个Collector 归纳。比如我们先去掉 servers 中元素的首字母,然后将它们装入 List

 // [elordcn, omcat, etty, ndertow, esin]
 servers.stream.collect(Collectors.mapping(s -> s.substring(1), Collectors.toList()));

有点类似 Stream 先进行了 map 操作再进行 collect

 servers.stream.map(s -> s.substring(1)).collect(Collectors.toList());

3.11 reducing

这个方法非常有用!但是如果要了解这个就必须了解其参数 BinaryOperator<T> 。 这是一个函数式接口,是给两个相同类型的量,返回一个跟这两个量相同类型的一个结果,伪表达式为 (T,T) -> T。默认给了两个实现 maxByminBy ,根据比较器来比较大小并分别返回最大值或者最小值。当然你可以灵活定制。然后 reducing 就很好理解了,元素两两之间进行比较根据策略淘汰一个,随着轮次的进行元素个数就是 reduce 的。那这个有什么用处呢? Java 官方给了一个例子:统计每个城市个子最高的人。

 Comparator<Person> byHeight = Comparator.comparing(Person::getHeight);
   Map<String, Optional<Person>> tallestByCity = people.stream()
             .collect(Collectors.groupingBy(Person::getCity, Collectors.reducing(BinaryOperator.maxBy(byHeight))));

结合最开始给的例子你可以使用 reducing 找出最长的字符串试试。

上面这一层是根据 Height 属性找最高的 Person ,而且如果这个属性没有初始化值或者没有数据,很有可能拿不到结果所以给出的是 Optional<Person>。 如果我们给出了 identity 作一个基准值,那么我们首先会跟这个基准值进行 BinaryOperator 操作。
比如我们给出高于 2 米 的人作为 identity。 我们就可以统计每个城市不低于 2 米 而且最高的那个人,当然如果该城市没有人高于 2 米则返回基准值identity

 Comparator<Person> byHeight = Comparator.comparing(Person::getHeight);
 Person identity= new Person();
      identity.setHeight(2.);
      identity.setName("identity");
   Map<String, Person> collect = persons.stream()
            .collect(Collectors.groupingBy(Person::getCity, Collectors.reducing(identity, BinaryOperator.maxBy(byHeight))));

这时候就确定一定会返回一个 Person 了,最起码会是基准值identity 不再是 Optional

还有些情况,我们想在 reducing 的时候把 Person 的身高先四舍五入一下。这就需要我们做一个映射处理。定义一个 Function<? super T, ? extends U> mapper 来干这个活。那么上面的逻辑就可以变更为:

  Comparator<Person> byHeight = Comparator.comparing(Person::getHeight);
    Person identity = new Person();
    identity.setHeight(2.);
    identity.setName("identity");
    // 定义映射 处理 四舍五入
    Function<Person, Person> mapper = ps -> {
      Double height = ps.getHeight();

      BigDecimal decimal = new BigDecimal(height);
      Double d = decimal.setScale(1, BigDecimal.ROUND_HALF_UP).doubleValue();
      ps.setHeight(d);
      return ps;
    };
    Map<String, Person> collect = persons.stream()
        .collect(Collectors.groupingBy(Person::getCity, Collectors.reducing(identity, mapper, BinaryOperator.maxBy(byHeight))));

4. 总结

今天我们对 Java 8 中的 Collectors 进行了详细的讲解。如果你熟悉了 Collectors 操作 Stream 会更加得心应手。当然在 Java 8 之后的 Java 9Java 12Collectors 都有新增的功能, 后面有时间我们会继续进行讲解。敬请关注!

更多关于java算法相关内容感兴趣的读者可查看本站专题:《Java文件与目录操作技巧汇总》、《Java数据结构与算法教程》、《Java操作DOM节点技巧总结》和《Java缓存操作技巧汇总》

希望本文所述对大家java程序设计有所帮助。

时间: 2020-05-19

详解Java8 Collect收集Stream的方法

Collection, Collections, collect, Collector, Collectos Collection是Java集合的祖先接口. Collections是java.util包下的一个工具类,内涵各种处理集合的静态方法. java.util.stream.Stream#collect(java.util.stream.Collector<? super T,A,R>)是Stream的一个函数,负责收集流. java.util.stream.Collector 是一个收

Java8新特性Stream流实例详解

什么是Stream流? Stream流是数据渠道,用于操作数据源(集合.数组等)所生成的元素序列. Stream的优点:声明性,可复合,可并行.这三个特性使得stream操作更简洁,更灵活,更高效. Stream的操作有两个特点:可以多个操作链接起来运行,内部迭代. Stream可分为并行流与串行流,Stream API 可以声明性地通过 parallel() 与sequential() 在并行流与顺序流之间进行切换.串行流就不必再细说了,并行流主要是为了为了适应目前多核机器的时代,提高系统CP

Java8 Stream API 详细使用方法与操作技巧指南

本文实例讲述了Java8 Stream API 详细使用方法与操作技巧.分享给大家供大家参考,具体如下: 1. 概述 Java 8 引入的一个重要的特性无疑是 Stream API.Stream 翻译过来是"流",突然想到的是大数据处理有个流式计算的概念,数据通过管道经过一个个处理器(Handler)进行筛选,聚合,而且流都具有向量性,强调的是对数据的计算处理,而集合强调的是数据集.Stream可以看做是一个可操作的数据集序列,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找.

java8 stream的分组功能实例介绍

前言 最近,项目开发时遇到一个问题.根据业务要求,前端给后端上送的参数是一个列表(如List list),因此,后端也用了一个列表来接收.然而,等后端拿到数据后,我发现我需要对相同classId的数据进行统一处理.于是,我找到前端妹妹讨论,看她能不能帮忙把相同classId的数据封装成列表传给我.我好将接收参数修改成以下格式(List list): class Dto{ String classId; List<Student> list; } 这时,前端妹妹评估了下改动程度,眼泪汪汪地看着我

Java9 Stream Collectors新增功能(小结)

Java 9 Stream Collectors新增功能 Java 8 引入Collectors,用于累加输入元素至可变的容器如,Map.List以及Set.本文看看Java 9 新增的两个Collectors:Collectors.filtering 和 Collectors.flatMapping,主要用于和 Collectors.groupingBy 一起提供智能的元素集合. Collectors.filtering方法 Collectors.filtering方法类似于Stream fi

Java8中Stream使用的一个注意事项

Stream简介 我们先来看看Java里面是怎么定义Stream的: A sequence of elements supporting sequential and parallel aggregate operations. 我们来解读一下上面的那句话: Stream是元素的集合,这点让Stream看起来用些类似Iterator: 可以支持顺序和并行的对原Stream进行汇聚的操作: 大家可以把Stream当成一个高级版本的Iterator.原始版本的Iterator,用户只能一个一个的遍历

java8中Stream的使用示例教程

前言 Java8中提供了Stream对集合操作作出了极大的简化,学习了Stream之后,我们以后不用使用for循环就能对集合作出很好的操作. 本文将给大家详细介绍关于java8 Stream使用的相关内容,下面话不多说了,来一起看看详细的介绍吧 1. 原理 Stream 不是集合元素,它不是数据结构并不保存数据,它是有关算法和计算的,它更像一个高级版本的 Iterator. 原始版本的 Iterator,用户只能显式地一个一个遍历元素并对其执行某些操作: 高级版本的 Stream,用户只要给出需

Java 8 Stream Api 中的 map和 flatMap 操作方法

1.前言 Java 8提供了非常好用的 Stream API ,可以很方便的操作集合.今天我们来探讨两个 Stream中间操作 map(Function<? super T, ? extends R> mapper) 和 flatMap(Function<? super T, ? extends Stream<? extends R>> mapper) 2. map 操作 map 操作是将流中的元素进行再次加工形成一个新流.这在开发中很有用.比如我们有一个学生集合,我们

Java 8 Stream操作类型及peek示例解析

简介 java 8 stream作为流式操作有两种操作类型,中间操作和终止操作.这两种有什么区别呢? 我们看一个peek的例子: Stream<String> stream = Stream.of("one", "two", "three","four"); stream.peek(System.out::println); 上面的例子中,我们的本意是打印出Stream的值,但实际上没有任何输出. 为什么呢? 中间

基于Java8 Stream API实现数据抽取收集

目标&背景 我们以"处理订单数据"为例,假设我们的应用是一个分布式应用,有"订单应用","物流应用","商品应用"等都是独立的服务.本次我们的目的需要展示订单列表完整数据: 1.查询订单列表. 2.批量查询物流信息. 3.将物流信息填充到订单主信息中. 假设我们定义了一个订单类,具有几个关键的属性:订单号,状态,订单价,快递信息.如下所示: class Order{ String orderSeq; String st

java8使用Stream API方法总结

Stream是java8中处理集合的关键抽象概念,它可以指定您希望对集合进行的操作,可以执行非常复杂的查找.过滤和映射数据等操作.使用Stream API对集合数据进行操作,就类似于使用SQL执行的数据库查询. Stream 的三个操作步骤 1.创建Stream. 得到Stream流的第一种方式: 可以通过Collection系列集合提供提供的Stream()或parallelStream @Test public void test1() { //可以通过Collection系列集合提供提供的

Java8中Lambda表达式使用和Stream API详解

前言 Java8 的新特性:Lambda表达式.强大的 Stream API.全新时间日期 API.ConcurrentHashMap.MetaSpace.总得来说,Java8 的新特性使 Java 的运行速度更快.代码更少.便于并行.最大化减少空指针异常. 0x00. 前置数据 private List<People> peoples = null; @BeforeEach void before () { peoples = new ArrayList<>(); peoples

如何利用Java8 Stream API对Map按键或值排序

一.什么是Java 8 Stream 使用Java 8 Streams,我们可以按键和按值对映射进行排序.下面是它的工作原理: Java Stream函数式编程?用过都说好,案例图文详解送给你 将Map或List等集合类对象转换为Stream对象 使用Streams的sorted()方法对其进行排序 最终将其返回为LinkedHashMap(可以保留排序顺序) sorted()方法以Comparator作为参数,从而可以按任何类型的值对Map进行排序.如果对Comparator不熟悉,可以看本号

使用 Java8 实现观察者模式的方法(下)

在上篇文章给大家介绍了使用Java8 实现观察者模式的方法(上),本文继续给大家介绍java8观察者模式相关知识,具体内容如下所述: 线程安全的实现 前面章节介绍了在现代Java环境下的实现观察者模式,虽然简单但很完整,但这一实现忽略了一个关键性问题:线程安全.大多数开放的Java应用都是多线程的,而且观察者模式也多用于多线程或异步系统.例如,如果外部服务更新其数据库,那么应用也会异步地收到消息,然后用观察者模式通知内部组件更新,而不是内部组件直接注册监听外部服务. 观察者模式的线程安全主要集中

使用Java8实现观察者模式的方法(上)

观察者(Observer)模式又名发布-订阅(Publish/Subscribe)模式,是四人组(GoF,即 Erich Gamma.Richard Helm.Ralph Johnson 和 John Vlissides)在1994合著的<设计模式:可复用面向对象软件的基础>中提出的(详见书中293-313页).尽管这种模式已经有相当长的历史,它仍然广泛适用于各种场景,甚至成为了标准Java库的一个组成部分.目前虽然已经有大量关于观察者模式的文章,但它们都专注于在 Java 中的实现,却忽视了

浅析Node.js 中 Stream API 的使用

本文由浅入深给大家介绍node.js stream api,具体详情请看下文吧. 基本介绍 在 Node.js 中,读取文件的方式有两种,一种是用 fs.readFile ,另外一种是利用 fs.createReadStream 来读取. fs.readFile 对于每个 Node.js 使用者来说最熟悉不过了,简单易懂,很好上手.但它的缺点是会先将数据全部读入内存,一旦遇到大文件的时候,这种方式读取的效率就非常低下了. 而 fs.createReadStream 则是通过 Stream 来读取

Java8中stream和functional interface的配合使用详解

前言 Java 8 提供了一组称为 stream 的 API,用于处理可遍历的流式数据.stream API 的设计,充分融合了函数式编程的理念,极大简化了代码量. 大家其实可以把Stream当成一个高级版本的Iterator.原始版本的Iterator,用户只能一个一个的遍历元素并对其执行某些操作:高级版本的Stream,用户只要给出需要对其包含的元素执行什么操作,比如"过滤掉长度大于10的字符串"."获取每个字符串的首字母"等,具体这些操作如何应用到每个元素上,

Java 8中Stream API的这些奇技淫巧!你Get了吗?

上次老师跟大家分享了 cookie.session和token,今天给大家分享一下Java 8中的Stream API. Stream简介 1.Java 8引入了全新的Stream API.这里的Stream和I/O流不同,它更像具有Iterable的集合类,但行为和集合类又有所不同. 2.stream是对集合对象功能的增强,它专注于对集合对象进行各种非常便利.高效的聚合操作,或者大批量数据操作. 3.只要给出需要对其包含的元素执行什么操作,比如 "过滤掉长度大于 10 的字符串".&