Java8中聚合操作collect、reduce方法详解

Stream的基本概念

Stream和集合的区别:

Stream不会自己存储元素。元素储存在底层集合或者根据需要产生。
Stream操作符不会改变源对象。相反,它会返回一个持有结果的新的Stream。
3.Stream操作可能是延迟执行的,这意味着它们会等到需要结果的时候才执行。
Stream操作的基本过程,可以归结为3个部分:

创建一个Stream。
在一个或者多个操作中,将指定的Stream转换为另一个Stream的中间操作。
通过终止(terminal)方法来产生一个结果。该操作会强制它之前的延时操作立即执行,这之后该Stream就不能再被使用了。
   中间操作都是filter()、distinct()、sorted()、map()、flatMap()等,其一般是对数据集的整理(过滤、排序、匹配、抽取等)。

    终止方法往往是完成对数据集中数据的处理,如forEach(),还有allMatch()、anyMatch()、findAny()、 findFirst(),数值计算类的方法有sum、max、min、average等等。终止方法也可以是对集合的处理,如reduce()、 collect()等等。reduce()方法的处理方式一般是每次都产生新的数据集,而collect()方法是在原数据集的基础上进行更新,过程中不产生新的数据集。

Listnums = Arrays.asList(1, 3, null, 8, 7, 8, 13, 10);
nums.stream().filter(num -> num != null).distinct().forEach(System.out::println);
  上面代码实现为过滤null值并去重,遍历结果,实现简洁明了。使用传统方法就相对繁琐的多。另外其中 forEach即为终止操作方法,如果无该方法上面代码就没有任何操作。filter、map、forEach、findAny等方法的使用都比较简单,这里省略。

下面介绍强大的聚合操作,其主要分为两种:

可变聚合:把输入的元素们累积到一个可变的容器中,比如Collection或者StringBuilder;
其他聚合:除去可变聚合,剩下的,一般都不是通过反复修改某个可变对象,而是通过把前一次的汇聚结果当成下一次的入参,反复如此。比如reduce,count,allMatch;
聚合操作reduce

    Stream.reduce,返回单个的结果值,并且reduce操作每处理一个元素总是创建一个新值。常用的方法有average, sum, min, max, count,使用reduce方法都可实现。这里主要介绍reduce方法:

T reduce(T identity, BinaryOperatoraccumulator)
   identity:它允许用户提供一个循环计算的初始值。accumulator:计算的累加器,其方法签名为apply(T t,U u),在该reduce方法中第一个参数t为上次函数计算的返回值,第二个参数u为Stream中的元素,这个函数把这两个值计算apply,得到的和会被赋值给下次执行这个方法的第一个参数。有点绕看代码:

int value = Stream.of(1, 2, 3, 4).reduce(100, (sum, item) -> sum + item);
Assert.assertSame(value, 110);
/* 或者使用方法引用 */
value = Stream.of(1, 2, 3, 4).reduce(100, Integer::sum);
  这个例子中100即为计算初始值,每次相加计算值都会传递到下一次计算的第一个参数。

reduce还有其它两个重载方法:

Optionalreduce(BinaryOperatoraccumulator):与上面定义基本一样,无计算初始值,所以他返回的是一个Optional。
U reduce(U identity, BiFunction accumulator, BinaryOperator combiner):与前面两个参数的reduce方法几乎一致,你只要注意到BinaryOperator其实实现了BiFunction和BinaryOperator两个接口。
收集结果collect
,?>

  当你处理完流时,通常只是想查看一下结果,而不是将他们聚合为一个值。先看collect的基础方法,它接受三个参数:

R collect(Suppliersupplier, BiConsumeraccumulator, BiConsumercombiner)
supplier:一个能创造目标类型实例的方法。accumulator:一个将当元素添加到目标中的方法。combiner:一个将中间状态的多个结果整合到一起的方法(并发的时候会用到)。接着看代码:,r>,?>

Streamstream = Stream.of(1, 2, 3, 4).filter(p -> p > 2);
Listresult = stream.collect(() -> new ArrayList<>(), (list, item) -> list.add(item), (one, two) -> one.addAll(two));
/* 或者使用方法引用 */
result = stream.collect(ArrayList::new, List::add, List::addAll);
这个例子即为过滤大于2的元素,将剩余结果收集到一个新的list中。

第一个方法生成一个新的ArrayList;
第二个方法中第一个参数是前面生成的ArrayList对象,第二个参数是stream中包含的元素,方法体就是把stream中的元素加入ArrayList对象中。第二个方法被反复调用直到原stream的元素被消费完毕;
第三个方法也是接受两个参数,这两个都是ArrayList类型的,方法体就是把第二个ArrayList全部加入到第一个中;
代码有点繁琐,或者使用collect的另一个重载方法:

R collect(Collector<? super="" r=""> collector)
注意到Collector其实是上面supplier、accumulator、combiner的聚合体。那么上面代码就变成:?>,a>

Listlist = Stream.of(1, 2, 3, 4).filter(p -> p > 2).collect(Collectors.toList());
将结果收集到map中

先定义如下Person对象

class Person{
    public String name;
    public int age;

    Person(String name, int age){
      this.name = name;
      this.age = age;
    }

    @Override
    public String toString(){
      return String.format("Person{name='%s', age=%d}", name, age);
    }
  }
假设你有一个Stream对象,希望将其中元素收集到一个map中,这样就可以根据他的名称来查找对应年龄,例如:

Mapresult = people.collect(HashMap::new,(map,p)->map.put(p.name,p.age),Map::putAll);
/*使用Collectors.toMap形式*/
Mapresult = people.collect(Collectors.toMap(p -> p.name, p -> p.age, (exsit, newv) -> newv));
其中Collectors.toMap方法的第三个参数为键值重复处理策略,如果不传入第三个参数,当有相同的键时,会抛出一个IlleageStateException。,>,>

或者你想将Person分解为Map存储:

List<Map> personToMap = people.collect(ArrayList::new, (list, p) -> {
   Mapmap = new HashMap<>();
   map.put("name", p.name);
   map.put("age", p.age);
   list.add(map);
}, List::addAll);
分组和分片,>,>

对具有相同特性的值进行分组是一个很常见的任务,Collectors提供了一个groupingBy方法,方法签名为:

Collector<T,?,Map> groupingBy(Function<? super="" extends="" k=""> classifier, Collector<? super="" d=""> downstream)
classifier:一个获取Stream元素中主键方法。downstream:一个操作对应分组后的结果的方法。?>?>,d>,k,a,d>

假如要根据年龄来分组:

Map<Integer, List> peropleByAge = people.filter(p -> p.age > 12).collect(Collectors.groupingBy(p -> p.age, Collectors.toList()));
假如我想要根据年龄分组,年龄对应的键值List存储的为Person的姓名,怎么做呢:

Map<Integer, List> peropleByAge = people.collect(Collectors.groupingBy(p -> p.age, Collectors.mapping((Person p) -> p.name, Collectors.toList())));
mapping即为对各组进行投影操作,和Stream的map方法基本一致。

假如要根据姓名分组,获取每个姓名下人的年龄总和(好像需求有些坑爹):

MapsumAgeByName = people.collect(Collectors.groupingBy(p -> p.name, Collectors.reducing(0, (Person p) -> p.age, Integer::sum)));
/* 或者使用summingInt方法 */
sumAgeByName = people.collect(Collectors.groupingBy(p -> p.name, Collectors.summingInt((Person p) -> p.age)));
可以看到Java8的分组功能相当强大,当然你还可以完成更复杂的功能。另外Collectors中还存在一个类似groupingBy的方法:partitioningBy,它们的区别是partitioningBy为键值为Boolean类型的groupingBy,这种情况下它比groupingBy更有效率。,>

join和统计功能

话说Java8中新增了一个StringJoiner,Collectors的join功能和它基本一样。用于将流中字符串拼接并收集起来,使用很简单:

String names = people.map(p->p.name).collect(Collectors.joining(","))
Collectors分别提供了求平均值averaging、总数couting、最小值minBy、最大值maxBy、求和suming等操作。但是假如你希望将流中结果聚合为一个总和、平均值、最大值、最小值,那么Collectors.summarizing(Int/Long/Double)就是为你准备的,它可以一次行获取前面的所有结果,其返回值为(Int/Long/Double)SummaryStatistics。

DoubleSummaryStatistics dss = people.collect(Collectors.summarizingDouble((Person p)->p.age));
double average=dss.getAverage();
double max=dss.getMax();
double min=dss.getMin();
double sum=dss.getSum();
double count=dss.getCount();

时间: 2024-10-22 13:21:46

Java8中聚合操作collect、reduce方法详解的相关文章

Javascript中的迭代、归并方法详解_基础知识

迭代方法 在Javascript中迭代方法个人觉得尤为重要,在很多时候都会有实际上的需求,javascript提供了5个迭代方法来供我们操作,它们分别为: every() 对数组中的每一个项运用给定的函数,如果每项都返回true,那么就会返回true filter() 对数组中的每一个项运用给定的函数,把返回true的项组成一个新数组并返回 forEach() 对数组中的每一项运用给定的函数,但是没有任何的返回值 map() 对数组中的每一个项运用给定的函数并返回每次函数调用的结果组成新的数组

js基础之DOM中元素对象的属性方法详解_javascript技巧

在 HTML DOM (文档对象模型)中,每个部分都是节点. 节点是DOM结构中最基本的组成单元,每一个HTML标签都是DOM结构的节点. 文档是一个    文档节点 . 所有的HTML元素都是    元素节点 所有 HTML 属性都是    属性节点 文本插入到 HTML 元素是    文本节点 注释是    注释节点. 最基本的节点类型是Node类型,其他所有类型都继承自Node,DOM操作往往是js中开销最大的部分,因而NodeList导致的问题最多.要注意:NodeList是'动态的',

基于js对象,操作属性、方法详解_javascript技巧

一,概述 在Java语言中,我们可以定义自己的类,并根据这些类创建对象来使用,在Javascript中,我们也可以定义自己的类,例如定义User类.Hashtable类等等. 目前在Javascript中,已经存在一些标准的类,例如Date.Array.RegExp.String.Math.Number等等,这为我们编程提供了许多方便.但对于复杂的客户端程序而言,这些还远远不够. 与Java不同,Java2提供给我们的标准类很多,基本上满足了我们的编程需求,但是Javascript提供的标准类很

Lua中break语句的使用方法详解

  这篇文章主要介绍了Lua中break语句的使用方法详解,是Lua入门学习中的基础知识,需要的朋友可以参考下 当循环中遇到break语句,循环立即终止,程序控制继续下一个循环语句后面. 如果您正在使用嵌套循环(即一个循环里面另一个循环),break 语句将停止最内层循环的执行并开始执行的下一行代码的程序后段. 语法 Lua break语句语法如下: 代码如下: break 例子: 代码如下: --[ local variable definition --] a = 10--[ while l

JavaScript中关键字 in 的使用方法详解_javascript技巧

for-in循环应该用在非数组对象的遍历上,使用for-in进行循环也被称为"枚举". 对于数组 ,迭代出来的是数组元素 但不推荐,因为不能保证顺序,而且如果在Array的原型上添加了属性,这个属性也会被遍历出来,所以 最好数组使用正常的for循环,对象使用for-in循环 对于对象 ,迭代出来的是对象的属性: var obj = { "key1":"value1", "key2":"value2", &q

Struts中使用validate()输入校验方法详解_java

1.在ActionSupport中有一个validate()方法,这个方法是验证方法,它会在execute()方法执行之前执行,所以能够起到很好的验证的作用. @Override //重写Action中的validate()方法 public void validate() { if(null==this.username||this.username.length()<4||this.username.length()>6){ this.addActionError("userna

Java多线程编程中使用Condition类操作锁的方法详解_java

Condition的作用是对锁进行更精确的控制.Condition中的await()方法相当于Object的wait()方法,Condition中的signal()方法相当于Object的notify()方法,Condition中的signalAll()相当于Object的notifyAll()方法.不同的是,Object中的wait(),notify(),notifyAll()方法是和"同步锁"(synchronized关键字)捆绑使用的:而Condition是需要与"互斥

必知技巧:ASP.NET中常用的优化性能方法详解

asp.net|技巧|详解|性能|优化 1. 数据库访问性能优化 数据库的连接和关闭 访问数据库资源需要创建连接.打开连接和关闭连接几个操作.这些过程需要多次与数据库交换信息以通过身份验证,比较耗费服务器资源.ASP.NET中提供了连接池(Connection Pool)改善打开和关闭数据库对性能的影响.系统将用户的数据库连接放在连接池中,需要时取出,关闭时收回连接,等待下一次的连接请求. 连接池的大小是有限的,如果在连接池达到最大限度后仍要求创建连接,必然大大影响性能.因此,在建立数据库连接后

Java面向对象编程中final关键字的使用方法详解_java

在Java中通过final关键字来声明对象具有不变性(immutable),这里的对象包括变量,方法,类,与C++中的const关键字效果类似. immutable指对象在创建之后,状态无法被改变 可以从三个角度考虑使用final关键字: 代码本身:不希望final描述的对象所表现的含义被改变 安全:final对象具有只读属性,是线程安全的 效率:无法修改final对象本身,对其引用的操作更为高效 final 变量定义final Object a,则a只能被初始化一次,一旦初始化,a的数据无法修