lambda 分类聚合
总览
在本文中,我们将介绍使用Java 8 lambda,流和聚合来过滤和处理Collection中的对象。 这篇文章中的所有代码都可以在此处的 BitBucket中找到 。
对于此示例,我们将创建许多对象,这些对象代表我们IT基础架构中的服务器。 我们将这些对象添加到列表中,然后使用lambda,流和聚合基于某些条件从列表中检索服务器。
目标
- 介绍lambda,流和聚合操作的概念。
- 解释流和管道之间的关系。
- 比较和对比聚合操作和迭代器。
- 演示过滤器,collect,forEach,mapToLong,average和getAsDouble聚合操作。
Lambdas
Lambda是Java的一项新功能,允许我们将功能或行为作为参数传递给方法。 一个说明Lambda有用性的示例来自UI编码。 当用户单击用户界面上的按钮时,通常会导致应用程序中发生某些操作。 在这种情况下,我们确实希望将行为传递给onClick(…)方法,以便应用程序将在单击按钮时执行给定的行为。 在Java的早期版本中,我们通过将匿名内部类(实现了已知接口)传递给方法来实现此目的。 在这种情况下使用的接口通常仅包含一个方法,该方法定义了我们希望传递给onClick(…)方法的行为。 尽管这可行,但语法不方便。 匿名内部类仍可用于此目的,但是新的Lambda语法更加简洁。
综合运营
当我们使用Collections将对象存储在程序中时,通常需要做的不仅仅是简单地将对象放入集合中-我们需要存储,检索,删除和更新这些对象。 聚合操作使用lambda对集合中的对象执行操作。 例如,您可以使用聚合操作来:
- 打印来自特定制造商的库存中所有服务器的名称
- 退还清单中早于特定期限的所有服务器
- 计算并返回清单中服务器的平均使用期限(前提是“服务器”对象具有“购买日期”字段)
所有这些任务都可以通过使用聚合操作以及管道和流来完成。 我们将在下面看到这些操作的示例。
管道和流
流水线只是一系列聚合操作。 流是一个项目序列,而不是数据结构,它通过管道从源头携带项目。 管道由以下部分组成:
- 数据源。 最常见的是这是一个Collection,但它可能是一个数组,方法调用的返回结果或某种I / O通道。
- 零个或多个中间操作。 例如,过滤操作。 中间操作产生新的流。 过滤器操作接收一个流,然后生成另一个流,该流仅包含与过滤器条件匹配的项目。
- 终端操作。 终端操作返回非流结果。 此结果可能是原始类型(例如,整数),Collection,或者根本没有结果(例如,该操作可能只是打印流中每个项目的名称)。
一些聚合操作(例如forEach)看起来像迭代器,但是它们有根本的区别:
- 聚合操作使用内部迭代。 您的应用程序无法控制元素的处理方式或时间(没有next()方法)。
- 聚合操作从流而不是直接从集合中处理项目。
- 聚合操作支持Lambda表达式作为参数。
Lambda语法
既然我们已经讨论了与Lambda表达式有关的概念,现在该看看它们的语法了。 您可以将Lambda表达式视为匿名方法,因为它们没有名称。 Lambda语法包含以下内容:
- 用括号分隔的形式参数的逗号分隔列表。 Lambda表达式中可以省略参数的数据类型。 如果只有一个形式参数,则可以省略括号。
- 箭头标记:->
- 由单个表达式或代码块组成的主体。
使用Lambda,流和聚合操作
如概述中所述,我们将通过过滤和检索List中的Server对象来演示lambda,流和聚合的用法。 我们来看四个例子:
- 查找并打印来自特定制造商的所有服务器的名称。
- 查找并打印超过一定年限的所有服务器的名称。
- 查找并提取新的列表到一定年限内的所有服务器,然后在新列表中打印服务器的名称。
- 计算并显示列表中服务器的平均寿命。
让我们开始吧…
服务器类
首先,我们来看一下Server类。 Server类将跟踪以下内容:
- 服务器名称
- 服务器的IP地址
- 制造商
- 内存容量(GB)
- 处理器数量
- 购买日期(LocalDate)
注意(在第65行),我们添加了getServerAge()
方法,该方法根据购买日期计算服务器的getServerAge()
以年为单位)–在计算服务器中的平均寿命时,将使用此方法库存。
创建和加载服务器
现在我们有了Server类,我们将创建一个List并加载几台服务器:
示例1:打印所有Dell服务器的名称
对于第一个示例,我们将编写一些代码来查找Dell制造的所有服务器,然后将服务器名称打印到控制台:
我们的第一步是在第76行–我们必须从服务器列表中获取流。 一旦有了流,就在第77行添加过滤器中间操作。过滤器操作将服务器流作为输入,然后生成另一个服务器流,该服务器流仅包含与过滤器lambda中指定的条件匹配的服务器。 我们仅选择Dell使用以下lambda制造的服务器: s -> s.getManufacturer().equalsIgnoreCase(manufacturer)
变量s表示从流中处理的每个服务器(请记住,我们不必声明类型)。 箭头运算符的右侧表示我们要为每个处理的服务器评估的语句。 在这种情况下,如果当前服务器的制造商是Dell,我们将返回true,否则返回false。 过滤器产生的输出流仅包含Dell制造的那些服务器。
最后,我们在第78行上添加了forEach终端操作。forEach操作将服务器流作为输入,然后在该流中的每个服务器上运行给定的lambda。 我们使用以下lambda将Dell服务器的名称打印到控制台: server -> System.out.println(server.getName())
请注意,我们在第一个lambda中使用s作为流中每个服务器的变量名,在第二个lambda中使用server作为变量名-它们不必从一个lambda匹配到下一个lambda。
上面代码的输出是我们期望的:
示例2:打印所有超过三年的所有服务器的名称
我们的第二个示例与第一个示例相似,不同之处在于我们要查找3年以上的服务器:
此示例与第一个示例之间的唯一区别是,我们将过滤器操作(第89行)中的lambda表达式更改为: s -> s.getServerAge() > age
该过滤器的输出流仅包含3年以上的服务器。
上面代码的输出是:
示例3:将所有超过3年的服务器提取到新列表中
我们的第三个示例与第二个示例相似,因为我们正在寻找使用三年以上的服务器。 本示例中的不同之处在于,我们将创建一个仅包含满足我们条件的服务器的新列表:
与前面的示例一样,我们从列表中获取流,并添加过滤器中间操作以创建仅包含3年以上服务器的流(第102和103行)。 现在,在第104行,我们使用collect终端操作而不是forEach终端操作。 collect终端操作将服务器流作为输入,然后将它们放入参数中指定的数据结构中。 在我们的例子中,我们将流转换为服务器列表。 结果列表由第100行上声明的oldServers变量引用。
最后,为了说明在本示例中获得的服务器与上一个服务器相同,我们在oldServers列表中打印所有服务器的名称。 请注意,因为我们希望列表中的所有服务器,所以没有中间过滤器操作。 我们只需从oldServers获取流,并将其提供给forEach终端操作即可。
输出是我们期望的:
示例4:计算并打印服务器的平均寿命
在最后一个示例中,我们将计算服务器的平均寿命:
第一步与前面的示例相同–我们从服务器列表中获取流。 接下来,我们添加mapToLong中间操作。 此聚合操作将服务器流作为输入,并生成Longs流作为输出。 根据第119行中指定的lambda将服务器映射到Longs(您也可以在第120行中使用等效语法)。 在这种情况下,我们要抓住每个传入服务器的年龄,并将其放入生成的Longs流中。
接下来,我们添加平均终端操作。 平均值完全符合您的期望-计算流中所有值的平均值。 诸如平均值之类的终端操作通过对流的内容进行合并或操作来返回一个值,这被称为归约操作 。 归约运算的其他示例包括sum , min , max和count 。
最后,我们添加操作getAsDouble 。 这是必需的,因为平均值返回类型OptionalDouble 。 如果传入流为空,则average将返回OptionalDouble的空实例。 如果发生这种情况,则调用getAsDouble将抛出NoSuchElementException ,否则它将仅在OptionalDouble实例中返回Double值。
该示例的输出为:
结论
我们仅涉及到您可以使用lambda,流和聚合进行处理的内容。 我鼓励您抓住源代码,使用它,并开始探索这些Java 8新功能的所有可能性。
翻译自: https://www.javacodegeeks.com/2015/12/using-java-8-lambdas-streams-aggregates.html
lambda 分类聚合