Java没基础函数式编程——Stream API 中的收集器

Posted 大数据与Java进阶

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java没基础函数式编程——Stream API 中的收集器相关的知识,希望对你有一定的参考价值。

前言

这是 Java 8 函数式编程系列的第二篇blog。在上篇,我们对 Java 8 的语法糖 Lambda 表达式以及部分 Stream API 进行了基本的学习,为了便于理解,我们写了一些简单的 Demo。详情请查看上一篇blog:


在今天的这篇blog中,我们来聊一下 Stream API 中更高级的应用。




方法引用

我们都说 Lambda 表达式是 Java 8 给程序员们提供的语法糖,那么 Java 8 也为 Lambda 表达式提供了语法糖,能把 Lambda 表示式变得更简单,叫做 方法引用,这可以帮助程序员更方便的重用已有方法。

如上:这是由一个普通的方法转换到使用 Lambda 表达式调用,然后使用方法引用来进行方法调用。


ClassName :: methodName 这是方法引用的标准语法。


虽然这同样是方法调用,但是不需要在后面加括号,因为这里并不调用该方法,这里只是提供了和 Lambda 表达式等价的一种结构,需要时才会调用。凡是使用 Lambda 表达式的地方,就可以使用方法引用。


对于构造函数,我们也可以使用同样的缩写形式,比如

【Java没基础】函数式编程——Stream API 中的收集器

方法引用自动支持多个参数, 前提是选对了正确的函数接口。


同样的,我们也可以使用这种方式来创建一个数组

【Java没基础】函数式编程——Stream API 中的收集器


凡是使用 Lambda 表达式的地方,就可以使用方法引用。方法引用只不过是基于这样的事实,提供了一种简短的语法而已。 ——Java 8 函数式编程




元素顺序

我们都知道,在 Java 中,一些集合类的元素是有序的:比如 List 等,有一些集合类的元素是无序的:比如 Set。


直观上来说,流是有序的,因为流中的元素都是按顺序进行处理,这种顺序成为出现顺序,出现顺序的定义依赖于数据源和流的操作。


在一个有序的集合中创建一个流,流中的元素就会按照顺序排列,因此,有序集合流的顺序测试永远可以通过

【Java没基础】函数式编程——Stream API 中的收集器


而如果集合的元素是无序的,比如 Set,因此生成的流也是无序的,比如 HashSet,因此不能保证每次断言测试都能通过。

【Java没基础】函数式编程——Stream API 中的收集器


有些集合本身是无序的,但这些操作有时会产生顺序。


在流操作中,有些中间操作会产生顺序,比如对值映射时,映射后的值是有序的,这种顺序就会保存下来。如果进来的流是无序的,出去的流也是无序的。

【Java没基础】函数式编程——Stream API 中的收集器


一些流操作具有很大的开销,调用unordered 方法消除这种顺序就能解决这种问题。大多数操作都是在有序流上的效率更高,比如 filter、map、reduce 等。


有时候,比如在使用并行流时,forEach 方法不能保证元素是按照顺序处理的。如果需要保证按顺序处理, 应该使用 forEachOrdered 方法。



收集器

收集器:一种通用的,从流中生成复杂值的结构,其实也是方法的一种。只需要将收集器传给collect 方法,所有的流就可以使用它了。Java 8 的标准库中已经提供了一些有用的收集器,他们在 java.util.stream.Collectors 中被定义。


集合转换

先介绍一个常用的收集器:toCollection。有时候,我们可能希望使用TreeSet,而不是由框架在背后自动为你指定一种类型的Set,此时就可以使用 toCollection ,它接受一个函数做参数来创建集合。

【Java没基础】函数式编程——Stream API 中的收集器


求值

Java 8 提供一些收集器用来求值,如 minBy(求最小值), maxBy(求最大值), averagingInt(求平均值), summingInt(求和)

【Java没基础】函数式编程——Stream API 中的收集器

通过调用 stream 方法让集合生成流, 然后调用 collect 方法收集结果。averagingInt 方法接受一个 Lambda 表达式作参数, 将流中的元素转换成一个整数, 然后再计算平均数。


分块和分组

对于一个集合,我们通常会有需求来集合中的元素进行分块或者分组操作。在 Java 8 之前,我们可能需要迭代这个集合,然后使用if语句、switch case 语句来对元素进行分组,而 Java 8 为我们提供了这样的两个收集器 partitioningBy 和 groupingBy


分块

partitioningBy 接受一个流,并将其分为两部分。它使用 Predicate 对象判断元素应该属于哪个部分,将数据分为 true 和 false 两部分,并根据其布尔值返回一个Map

【Java没基础】函数式编程——Stream API 中的收集器


分组

groupingBy: 比 partitioningBy 更自然,可以使用任意值对数据进行分组。

【Java没基础】函数式编程——Stream API 中的收集器


字符串

对于一个集合,我们可能需要需要获取他某个字段的字符信息,在 Java 8 之前我们通常采用迭代器,在一些特殊的情景中我们可能还需要加上前后缀字符。在 Java 8 之后,我们拥有了 joining 方法。

【Java没基础】函数式编程——Stream API 中的收集器


joining 可以方便的从一个流中得到一个字符串,并且可以提供分隔符,前缀与后缀。
它的一般格式是 Collectors.joining("分隔符", "前缀", "后缀").


组合收集器

对于不同的业务逻辑,我们对于集合的处理也会采用不同的方式,将收集器组合起来可以帮助我们完成更为复杂的功能。


在这里,我们来假设一个情景,然后思考一下应该怎样通过组合收集器的方式来实现。

【Java没基础】函数式编程——Stream API 中的收集器


在上边的代码中,我们先使用 groupingBy 根据汽车注册的城市来进行分组,然后使用下游的另一个收集器 counting 来计算每组的元素数量,将结果映射为 Map.


接着我们来讨论另一个情景,如果我们不想获取到每个城市下注册的汽车数量,而是想获取到每个城市下注册的汽车的车主名列表呢( 简单举例,假设只有十几辆汽车注册在几个城市中,对于大数据量的优化我们以后来进行讨论 )。

我们能够想到,先分组,然后再按照分组来获取每个城市下注册车辆的车主名。但是我们会发现,这里不能直接使用流的 map 操作, 因为列表是由 groupingBy 生成的。 我们需要有一种方法, 可以告诉 groupingBy 将它的值做映射, 生成最终结果。

每个收集器都是生成最终值的一剂良方。 这里需要两剂配方, 一个传给另一个。 谢天谢地, Oracle 公司的研究员们已经考虑到这种情况, 为我们提供了 mapping 收集器。 ——OpenJDK Lambda项目组

mapping 允许在收集器的容器上执行类似 map 的操作。 但是需要指明使用什么样的集合类存储结果, 比如 toList。


我们用到了第二个收集器, 用以收集最终结果的一个子集。 这些收集器叫作下游收集器。 收集器是生成最终结果的一剂配方, 下游收集器则是生成部分结果的配方,主收集器中会用到下游收集器。 这种组合使用收集器的方式, 使得它们在 Stream 类库中的作用更加强大。 —— Java 8 函数式编程

那些为基本类型特殊定制的函数, 如 averagingDouble、 summarizingLong 等, 事实上和调用特殊 Stream 上的方法是等价的, 加上它们是为了将它们当作下游收集器来使用的。


重构收集器

在 Java 类库中,为程序员们提供的收集器基本上完全可以满足项目需求。但是就像有时候需要重写 hashCode 和 equals 方法一样,收集器其实也是方法,那么我们也可以对收集器来进行重构。

具体可以参看 《Java 8 函数式编程》第五章重构和定制收集器一节。(我也在研究之中)



后记

对于Java的函数式编程中的 高级集合类与收集器 的一些话题我们先聊到这里,在下一篇blog中,我们来着重讨论一下并行流:这个大大提高流工作效率的技术。


在学习 Java 8 的函数式编程的过程中,我准备用三篇 blog 来对我的学习进行一个总结性的描述:

  • JDK1.8 中的 Lambda 表达式与 Stream API

  • Stream API 中的高级集合类与收集器

  • Java 8 函数式编程 —— 并行流


这里有个傲娇的二维码,关注一下呗亲~


以上是关于Java没基础函数式编程——Stream API 中的收集器的主要内容,如果未能解决你的问题,请参考以下文章

Java入门与基础(stream函数式编程代码示例)

强大的 Stream 函数式编程

Java8 那些事儿:Stream 函数式编程

[三]java8 函数式编程Stream 概念深入理解 Stream 运行原理 Stream设计思路

java8 函数式编程入门官方文档中文版 java.util.stream 中文版 流处理的相关概念

Java语言编程经验之基础语法20-Lambda&方法引用-21-函数式接口&Stream流