JAVA基础

Java基础 - Reduction

2019-05-15  本文已影响0人  HRocky

原文链接:https://docs.oracle.com/javase/tutorial/collections/streams/reduction.html

在"聚合操作"这一小节描述了下面的管道操作,就是计算集合roster中所有男性成员的平均年龄:

double average = roster
    .stream()
    .filter(p -> p.getGender() == Person.Sex.MALE)
    .mapToInt(Person::getAge)
    .average()
    .getAsDouble();

JDK包含许多终结操作(例如average、sum、min、max和count),它们通过组合流的内容返回一个值。这些操作称为归约操作。JDK还包返回集合而不是单个值的规约操作。许多归约操作执行特定任务,例如查找值的平均值或将元素分组到类别中。但是,JDK为您提供了通用的归约操作reduce和collect,本节将对此进行详细描述。

Stream.reduce方法

Stream.reduce方法是一种通用的归约操作。考虑以下管道,它计算集合roster中男性成员的年龄之和。它使用Stream.sum归约操作:

Integer totalAge = roster
    .stream()
    .mapToInt(Person::getAge)
    .sum();

将其与以下管道进行比较,该管道使用Stream.reduce操作来计算相同的值:

Integer totalAgeReduce = roster
   .stream()
   .map(Person::getAge)
   .reduce(0, (a, b) -> a + b);

本例中的reduce操作有两个参数:

reduce操作总是返回一个新值。但是,累加器函数每次处理流的元素时也返回一个新值。假设您希望将流的元素归约为更复杂的对象,例如集合。这可能会影响应用程序的性能。如果reduce操作涉及向集合添加元素,那么每次累加器函数处理元素时,它都会创建一个包含元素的新集合,这是效率低下的。您可以更有效地更新现有的集合。您可以使用Stream.collect方法来完成此操作,下一节将对此进行描述。

Stream.collect方法

reduce方法在处理元素时总是创建一个新值,与此不同的是,collect方法是修改或更改现有值。

考虑如何在流中找到值的平均值。您需要两条数据:值的总数和这些值的总和。但是,与reduce方法和所有其他归约方法一样,collect方法只返回一个值。您可以创建一个新的数据类型,该类型包含跟踪值总数和这些值之和的成员变量,例如以下类,Averager:

class Averager implements IntConsumer
{
    private int total = 0;
    private int count = 0;
        
    public double average() {
        return count > 0 ? ((double) total)/count : 0;
    }
        
    public void accept(int i) { total += i; count++; }
    public void combine(Averager other) {
        total += other.total;
        count += other.count;
    }
}

以下管道使用Averager类和collect方法计算所有男性成员的平均年龄:

Averager averageCollect = roster.stream()
    .filter(p -> p.getGender() == Person.Sex.MALE)
    .map(Person::getAge)
    .collect(Averager::new, Averager::accept, Averager::combine);
                   
System.out.println("Average age of male members: " +
    averageCollect.average());

本例中的collect操作有三个参数:

注意:

虽然dk为您提供了计算流中元素的平均值的average操作,但如果需要从流中的元素计算多个值,则可以使用collect操作和自定义类。

collect操作最适合于集合。下面的示例将男性成员的名称放入集合中,并使用collect操作:

List<String> namesOfMaleMembersCollect = roster
    .stream()
    .filter(p -> p.getGender() == Person.Sex.MALE)
    .map(p -> p.getName())
    .collect(Collectors.toList());

此版本的collect操作接受一个类型为Collector的参数。该类封装collect操作中用作参数的函数,这些函数需要三个参数(supplier、accumulator和combiner函数)。

Collection类包含许多有用的归约操作,例如将元素累加到集合中,并根据各种标准总结元素。这些归约操作返回Collector类的实例,因此可以将它们用作collect操作的参数。

此示例使用Collectiontors.toList操作,该操作将流元素累加到List的新实例中。与Collectors类中的大多数操作一样,toList运算符返回的是Collector的实例,而不是集合。

以下示例按性别将集合roster成员分组:

Map<Person.Sex, List<Person>> byGender =
    roster
        .stream()
        .collect(
            Collectors.groupingBy(Person::getGender));

The groupingBy operation returns a map whose keys are the values that result from applying the lambda expression specified as its parameter (which is called a classification function). In this example, the returned map contains two keys, Person.Sex.MALE and Person.Sex.FEMALE. The keys' corresponding values are instances of List that contain the stream elements that, when processed by the classification function, correspond to the key value. For example, the value that corresponds to key Person.Sex.MALE is an instance of List that contains all male members.

以下示例检索集合roster中每个成员的姓名,并按性别分组:

Map<Person.Sex, List<String>> namesByGender =
    roster
        .stream()
        .collect(
            Collectors.groupingBy(
                Person::getGender,                      
                Collectors.mapping(
                    Person::getName,
                    Collectors.toList())));

The groupingBy operation in this example takes two parameters, a classification function and an instance of Collector. The Collector parameter is called a downstream collector. This is a collector that the Java runtime applies to the results of another collector. Consequently, this groupingBy operation enables you to apply a collect method to the List values created by the groupingBy operator. This example applies the collector mapping, which applies the mapping function Person::getName to each element of the stream. Consequently, the resulting stream consists of only the names of members. A pipeline that contains one or more downstream collectors, like this example, is called a multilevel reduction.

以下示例检索每个性别成员的总年龄:

Map<Person.Sex, Integer> totalAgeByGender =
    roster
        .stream()
        .collect(
            Collectors.groupingBy(
                Person::getGender,                      
                Collectors.reducing(
                    0,
                    Person::getAge,
                    Integer::sum)));

reducing操作需要三个参数:

以下示例检索每个性别成员的平均年龄:

Map<Person.Sex, Double> averageAgeByGender = roster
    .stream()
    .collect(
        Collectors.groupingBy(
            Person::getGender,                      
            Collectors.averagingInt(Person::getAge)))
上一篇 下一篇

猜你喜欢

热点阅读