聚合操做一节描述了下列操做管道,计算集合roster
中全部男性成员的平均年龄:html
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操做,本节将详细介绍这些操做。java
你能够在示例ReductionExamples中找到本节中描述的代码摘录。git
Stream.reduce方法是一种通用的概括操做,考虑如下管道,它计算集合roster
中男性成员的年龄总和,它使用Stream.sum概括操做。github
Integer totalAge = roster .stream() .mapToInt(Person::getAge) .sum();
将其与下面使用流的管道进行比较,使用Stream.reduce
操做计算相同的值:segmentfault
Integer totalAgeReduce = roster .stream() .map(Person::getAge) .reduce( 0, (a, b) -> a + b);
本例中的reduce
操做有两个参数:api
identity
:标识元素既是概括的初始值,也是流中没有元素时的默认结果,在本例中,标示元素为0
,这是年龄总和的初始值,若是集合roster
中没有成员,则为默认值。accumulator
:累加器函数接受两个参数:概括的部分结果(在本例中,是到目前为止全部处理过的整数的和)和流的下一个元素(在本例中,是一个整数),它返回一个新的局部结果。在本例中,累加器函数是一个lambda表达式,它添加两个Integer
值并返回一个Integer
值:(a, b) -> a + b
。reduce
操做老是返回一个新值,可是,accumulator
函数每次处理流的元素时也会返回一个新值,假设你但愿将流的元素概括为更复杂的对象,例如集合,这可能会影响应用程序的性能。若是reduce
操做涉及向集合添加元素,那么每次累加器函数处理一个元素时,它都会建立一个包含该元素的新集合,这是低效的,相反,更新现有的集合将更有效,你可使用Stream.collect来实现这一点。与reduce
方法不一样,collect
方法修改或改变现有值,而reduce
方法在处理元素时老是建立一个新值。oracle
考虑如何找到流中值的平均值,你须要两段数据:值的总数和这些值的和。然而,与reduce
方法和全部其余概括方法同样,collect
方法只返回一个值,你能够建立一个包含成员变量的新数据类型,该成员变量跟踪值的总数和这些值的总和,例以下面的类Averager:app
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
方法计算全部男性成员的平均年龄:ide
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
操做接受三个参数:函数
supplier
:supplier
是个工厂方法,它构造新的实例,对于collect
操做,它建立结果容器的实例,在本例中,它是Averager
类的一个新实例。accumulator
:accumulator
函数将流元素合并到结果容器中,在本例中,它经过将count
变量增长1,并将流元素的值添加到total
成员变量中,该元素是一个整数,表示男性成员的年龄,来修改Averager
结果容器。combiner
:combiner
函数接受两个结果容器并合并它们的内容,在本例中,它经过将count
变量与另外一个Averager
实例的count
成员变量相加,并将另外一个Averager
实例的total
成员变量的值添加到total
成员变量中,从而修改Averager
结果容器。请注意如下:
supplier
是lambda表达式(或方法引用),而不是reduce
操做中的identity
元素之类的值。accumulator
和combiner
函数不返回值。collect
操做(若是你使用并行流运行collect
方法,那么每当combiner
函数建立一个新对象时,JDK都会建立一个新线程,例如本例中的Averager
对象,所以,你没必要担忧同步)。虽然JDK提供了计算流中元素平均值的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
函数)。
Collectors类包含许多有用的概括操做,好比将元素累积到集合中,并根据各类标准汇总元素,这些概括操做返回类Collector
的实例,所以能够将它们用做collect
操做的参数。
本例使用Collectors.toList操做,它将流元素累积到List
的新实例中,与Collectors
类中的大多数操做同样,toList
操做符返回Collector
的实例,而不是集合。
如下示例按性别将集合roster
的成员分组:
Map<Person.Sex, List<Person>> byGender = roster .stream() .collect( Collectors.groupingBy(Person::getGender));
groupingBy操做返回一个map,其键是应用指定为其参数的lambda表达式(称为分类函数)所获得的值。在本例中,返回的map包含两个键,Person.Sex.MALE
和Person.Sex.FEMALE
,键对应的值是List
的实例,其中包含流元素,当分类函数处理这些元素时,这些元素与键值对应。例如,与键Person.Sex.MALE
对应的值是一个包含全部男性成员的List
实例。
如下示例检索集合roster
中每一个成员的姓名,并按性别将其分组:
Map<Person.Sex, List<String>> namesByGender = roster .stream() .collect( Collectors.groupingBy( Person::getGender, Collectors.mapping( Person::getName, Collectors.toList())));
本例中的groupingBy操做接受两个参数,一个分类函数和一个Collector
实例,Collector
参数称为下游收集器,这是Java运行时应用于另外一个收集器的结果的收集器。所以,这个groupingBy
操做使你可以对groupingBy
操做符建立的List
值应用collect
方法。此示例应用收集器mapping,它将mapping
函数Person::getName
应用于流的每一个元素。所以,产生的流只包含成员的名称,包含一个或多个下游收集器的管道(如本例)称为多级概括。
下面的示例检索每种性别成员的总年龄:
Map<Person.Sex, Integer> totalAgeByGender = roster .stream() .collect( Collectors.groupingBy( Person::getGender, Collectors.reducing( 0, Person::getAge, Integer::sum)));
reducing操做须要三个参数:
identity
:如Stream.reduce
操做,若是流中没有元素,则identity
元素既是概括的初始值,也是缺省结果,在这个例子中,identity
元素是0
,这是年龄总和的初始值,若是不存在成员,则为默认值。mapper
:reducing
操做将此mapper
函数应用于全部流元素,在本例中,mapper
检索每一个成员的年龄。operation
:operation
函数用于概括映射值,在本例中,operation
函数添加Integer
值。下面的例子检索了每种性别成员的平均年龄:
Map<Person.Sex, Double> averageAgeByGender = roster .stream() .collect( Collectors.groupingBy( Person::getGender, Collectors.averagingInt(Person::getAge)));