Stream API与Lambda表达式的聚合操作

注:为了更好地理解本节中的内容,可查看 lambda表达式方法引用的相关内容。

  你为什么使用集合?你不只是简单地将对象存储在集合中,在大多数情况下,你使用集合来检索存储在其中的项目。

  再次考虑lambda表达式中描述的场景 。假设您正在创建一个社交网络应用程序。您希望创建一项功能,使管理员能够对满足特定条件的社交网络应用程序的成员执行任何类型的操作,例如发送消息。

  如同往常一样,假设这个社交网络应用程序的成员由以下Person类表示 :

public class Person {

    public enum Sex {
        MALE, FEMALE
    }

    String name;
    LocalDate birthday;
    Sex gender;
    String emailAddress;

    // ...

    public int getAge() {
        // ...
    }

    public String getName() {
        // ...
    }
}

  以下示例使用for-each循环打印集合roster中所包含的所有成员:

for (Person p : roster) {
    System.out.println(p.getName());
}

  以下示例使用聚合操作forEach打印集合roster中所包含的所有成员:

roster
    .stream()
    .forEach(e -> System.out.println(e.getName());

  虽然在本例中,使用聚合操作的版本比使用for-each循环的版本的代码更长,但对于更复杂的任务来说,您会看到使用批量数据操作的版本的代码将更加简洁。

1.管道和流(Pipelines and Streams)

  管道是一系列聚合操作的总和以下示例使用一个包含聚合操作filter和forEach的管道打印集合roster中所包含的所有男性成员:

roster
    .stream()
    .filter(e -> e.getGender() == Person.Sex.MALE)
    .forEach(e -> System.out.println(e.getName()));

  将此示例与下面使用for-each循环打印集合roster中包含的男性成员的示例进行比较:

for (Person p : roster) {
    if (p.getGender() == Person.Sex.MALE) {
        System.out.println(p.getName());
    }
}


管道包含以下组件:

  • 源头:这可以是一个集合,一个数组,一个生成器函数或一个I / O通道。在本例中,源头是集合roster。
  • 零个或多个中间操作。一个中间操作,例如filter,会产生一个新的流。

流(Stream)是元素的序列。与集合不同,它不是存储元素的数据结构。流通过管道从源头传送值(values)。本示例通过调用方法stream()从集合roster中创建一个流。

filter操作返回一个新的流,其中包含该断言(此操作的参数)相匹配的元素。在本例中,断言是lambda表达式e -> e.getGender() == Person.Sex.MALE。如果对象e的性别字段的值为Person.Sex.MALE,则返回布尔值true。因此,本例中的filter操作将返回包含集合roster中所有男性成员的流。

  • 终端操作。终端操作,例如forEach,会产生非流结果,诸如基本类型的值(例如double类型的值)、集合,或者在终端操作为forEach的情况下没有值。在本例中,forEach操作的参数是lambda表达式e -> System.out.println(e.getName()),它调用e对象的getName()方法。(Java编译器会推断对象的类型e是Person。)

      以下示例使用一个包含聚合操作filter、mapToInt和average的管道计算集合roster中所包含的所有男性成员的平均年龄:

double average = roster
    .stream()                                       // Stream< Person >  
    .filter(p -> p.getGender() == Person.Sex.MALE)  // Stream< Person >
    .mapToInt(Person::getAge)                       // IntStream  
    .average()                                      // OptionalDouble
    .getAsDouble();                                 // Double  

  该mapToInt操作会返回一个新的IntStream类型(这是一个只包含整数值的流)的流。该操作将其参数中指定的函数应用于特定流中的每个元素。在这个例子中,函数是Person::getAge,是一个返回成员年龄的方法引用。(或者,您可以使用lambda表达式e -> e.getAge()。)因此,此示例中mapToInt的操作会返回集合roster中包含所有男性成员年龄的流。

  该average操作会计算流中的IntStream类型的所有元素的平均值。它返回一个OptionalDouble类型的对象。如果流中没有元素,那么average操作会返回一个OptionalDouble类型的空的实例,并在调用方法getAsDouble时抛出一个NoSuchElementException异常。JDK包含许多终端操作,例如通过组合流的所有内容得到一个值的average操作。这些操作被称为归约操作,更多有关信息请参阅官方文档

2.聚合操作和迭代器之间的区别

聚合操作,如forEach,就像是迭代器一样。但是,它们有几个基本的区别:

  • 聚合操作使用内部迭代:聚合操作不包含像next这样指示它们处理集合的下一个元素的方法。当使用内部委派时,您的应用程序决定哪些集合进行迭代,但是由JDK决定如何遍历集合。当使用外部迭代时,您的应用程序将决定迭代的集合以及如何迭代它。但是,外部迭代只能依次迭代集合的元素。而内部迭代没有这个限制。它可以更容易地利用并行计算,这涉及到将问题分解为子问题,同时解决这些问题,然后将子问题的结果结合起来。更多有关信息请参见官方文档
  • 聚合操作从流中处理元素:聚合操作从流中处理元素,而不是直接从集合中处理。因此,他们也被称为流操作
  • 聚合操作支持将行为作为参数:在大多数聚合操作中,可以指定lambda表达式为参数。这使您可以自定义特定聚合操作的行为。


在本节的示例中所描述的代码片段: BulkDataOperationsExamples
原文链接:Aggregate Operation
译文链接:http://blog.csdn.net/why_still_confused/article/details/78965263
版权声明:本文为博主原创翻译文章,若要转载请注明文章出处

猜你喜欢

转载自blog.csdn.net/why_still_confused/article/details/78965263