Java 8 Stream 的终极技巧——Collectors 操作

2024-02-20 06:08

本文主要是介绍Java 8 Stream 的终极技巧——Collectors 操作,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

1. 前言

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

2. Collectors 的作用

Collectors 是 Java 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 使用 Stream 的 collect 方法中的转换为熟悉的集合容器。非常简单这里不再演示。

3.2 joining

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

 //   输出 FelordcnTomcatJettyUndertowResinservers.stream().collect(Collectors.joining());//   输出 Felordcn,Tomcat,Jetty,Undertow,Resinservers.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))

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

 //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

用来做累加计算。计算元素某个属性的总和,类似 Mysql 的 sum 函数,比如计算各个项目的盈利总和、计算本月的全部工资总和等等。我们这里就计算一下 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。默认给了两个实现 maxBy 和 minBy ,根据比较器来比较大小并分别返回最大值或者最小值。当然你可以灵活定制。然后 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 9 和 Java 12 中 Collectors 都有新增的功能, 后面有时间我们会继续进行讲解。敬请关注!

这篇关于Java 8 Stream 的终极技巧——Collectors 操作的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/727267

相关文章

Java方法重载与重写之同名方法的双面魔法(最新整理)

《Java方法重载与重写之同名方法的双面魔法(最新整理)》文章介绍了Java中的方法重载Overloading和方法重写Overriding的区别联系,方法重载是指在同一个类中,允许存在多个方法名相同... 目录Java方法重载与重写:同名方法的双面魔法方法重载(Overloading):同门师兄弟的不同绝

Spring配置扩展之JavaConfig的使用小结

《Spring配置扩展之JavaConfig的使用小结》JavaConfig是Spring框架中基于纯Java代码的配置方式,用于替代传统的XML配置,通过注解(如@Bean)定义Spring容器的组... 目录JavaConfig 的概念什么是JavaConfig?为什么使用 JavaConfig?Jav

Java数组动态扩容的实现示例

《Java数组动态扩容的实现示例》本文主要介绍了Java数组动态扩容的实现示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧... 目录1 问题2 方法3 结语1 问题实现动态的给数组添加元素效果,实现对数组扩容,原始数组使用静态分配

Java中ArrayList与顺序表示例详解

《Java中ArrayList与顺序表示例详解》顺序表是在计算机内存中以数组的形式保存的线性表,是指用一组地址连续的存储单元依次存储数据元素的线性结构,:本文主要介绍Java中ArrayList与... 目录前言一、Java集合框架核心接口与分类ArrayList二、顺序表数据结构中的顺序表三、常用代码手动

JAVA项目swing转javafx语法规则以及示例代码

《JAVA项目swing转javafx语法规则以及示例代码》:本文主要介绍JAVA项目swing转javafx语法规则以及示例代码的相关资料,文中详细讲解了主类继承、窗口创建、布局管理、控件替换、... 目录最常用的“一行换一行”速查表(直接全局替换)实际转换示例(JFramejs → JavaFX)迁移建

Spring Boot Interceptor的原理、配置、顺序控制及与Filter的关键区别对比分析

《SpringBootInterceptor的原理、配置、顺序控制及与Filter的关键区别对比分析》本文主要介绍了SpringBoot中的拦截器(Interceptor)及其与过滤器(Filt... 目录前言一、核心功能二、拦截器的实现2.1 定义自定义拦截器2.2 注册拦截器三、多拦截器的执行顺序四、过

Go异常处理、泛型和文件操作实例代码

《Go异常处理、泛型和文件操作实例代码》Go语言的异常处理机制与传统的面向对象语言(如Java、C#)所使用的try-catch结构有所不同,它采用了自己独特的设计理念和方法,:本文主要介绍Go异... 目录一:异常处理常见的异常处理向上抛中断程序恢复程序二:泛型泛型函数泛型结构体泛型切片泛型 map三:文

JAVA线程的周期及调度机制详解

《JAVA线程的周期及调度机制详解》Java线程的生命周期包括NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING和TERMINATED,线程调度依赖操作系统,采用抢占... 目录Java线程的生命周期线程状态转换示例代码JAVA线程调度机制优先级设置示例注意事项JAVA线程

Python中4大日志记录库比较的终极PK

《Python中4大日志记录库比较的终极PK》日志记录框架是一种工具,可帮助您标准化应用程序中的日志记录过程,:本文主要介绍Python中4大日志记录库比较的相关资料,文中通过代码介绍的非常详细,... 目录一、logging库1、优点2、缺点二、LogAid库三、Loguru库四、Structlogphp

JavaWeb项目创建、部署、连接数据库保姆级教程(tomcat)

《JavaWeb项目创建、部署、连接数据库保姆级教程(tomcat)》:本文主要介绍如何在IntelliJIDEA2020.1中创建和部署一个JavaWeb项目,包括创建项目、配置Tomcat服务... 目录简介:一、创建项目二、tomcat部署1、将tomcat解压在一个自己找得到路径2、在idea中添加