当前位置: 移动技术网 > IT编程>开发语言>Java > Java 8 Stream 的终极技巧——Collectors 操作

Java 8 Stream 的终极技巧——Collectors 操作

2020年01月03日  | 移动技术网IT编程  | 我要评论

魏如久,赛尔号吉宝怎么抓,专家系统

1. 前言

昨天在 collection移除元素操作 相关的文章中提到了 collectors 。相信很多同学对这个比较感兴趣,那我们今天就来研究一下 collectors

2. collectors 的作用

collectorsjava 8 加入的操作类,位于 java.util.stream 包下。它会根据不同的策略将元素收集归纳起来,比如最简单常用的是将元素装入mapsetlist 等可变容器中。特别对于 java 8 stream api 来说非常有用。它提供了collect() 方法来对 stream 流进行终结操作派生出基于各种策略的结果集。我们就借助于 stream 来熟悉一下 collectors 吧。我们依然用昨天的例子:

    list<string> servers = new arraylist<>();
        servers.add("felordcn");
        servers.add("tomcat");
        servers.add("jetty");
        servers.add("undertow");
        servers.add("resin");

3. java 8 中 collectors 的方法

collectors 提供了一系列的静态方法供我们使用,通常情况我们静态导入即可使用。接下来我们来看看都提供了哪些方法吧。

3.1 类型归纳

这是一个系列,作用是将元素分别归纳进可变容器 listmapsetcollection 或者concurrentmap

    collectors.tolist();
    collectors.tomap();
    collectors.toset();
    collectors.tocollection();
    collectors.toconcurrentmap();

我们可以根据以上提供的 api 使用 streamcollect 方法中的转换为熟悉的集合容器。非常简单这里不再演示。

3.2 joining

将元素以某种规则连接起来。该方法有三种重载 joining(charsequence delimiter)joining(charsequence delimiter,charsequence prefix,charsequence suffix)

 //   输出 felordcntomcatjettyundertowresin
 servers.stream().collect(collectors.joining());

 //   输出 felordcn,tomcat,jetty,undertow,resin
 servers.stream().collect(collectors.joining("," ));

 //   输出 [felordcn,tomcat,jetty,undertow,resin]
 servers.stream().collect(collectors.joining(",", "[", "]")); 

用的比较多的是读取 httpservletrequest 中的 body

  httpservletrequest.getreader().lines().collect(collectors.joining());

3.3 collectingandthen

该方法先执行了一个归纳操作,然后再对归纳的结果进行 function 函数处理输出一个新的结果。

 // 比如我们将servers joining 然后转成大写,结果为: felordcn,tomcat,jetty,undertow,resin   
 servers.stream.collect(collectors.collectingandthen(collectors.joining(","), string::touppercase));

3.4 groupingby

按照条件对元素进行分组,和 sql 中的 group by 用法有异曲同工之妙,通常也建议使用 java 进行分组处理以减轻数据库压力。groupingby 也有三个重载方法
我们将 servers 按照长度进行分组:

// 按照字符串长度进行分组    符合条件的元素将组成一个 list 映射到以条件长度为key 的 map<integer, list<string>> 中
servers.stream.collect(collectors.groupingby(string::length))

如果我不想 mapvaluelist 怎么办? 上面的实现实际上调用了下面的方式:

 //map<integer, set<string>>
 servers.stream.collect(collectors.groupingby(string::length, collectors.toset()))

我要考虑同步安全问题怎么办? 当然使用线程安全的同步容器啊,那前两种都用不成了吧! 别急! 我们来推断一下,其实第二种等同于下面的写法:

 supplier<map<integer,set<string>>> mapsupplier = hashmap::new;
 map<integer,set<string>> collect = servers.stream.collect(collectors.groupingby(string::length, mapsupplier, collectors.toset()));

这就非常好办了,我们提供一个同步 map 不就行了,于是问题解决了:

 supplier<map<integer, set<string>>> mapsupplier = () -> collections.synchronizedmap(new hashmap<>());
 map<integer, set<string>> collect = servers.stream.collect(collectors.groupingby(string::length, mapsupplier, collectors.toset()));

其实同步安全问题 collectors 的另一个方法 groupingbyconcurrent 给我们提供了解决方案。用法和 groupingby 差不多。

3.5 partitioningby

partitioningby 我们在本文开头的提到的文章中已经见识过了,可以看作 groupingby 的一个特例,基于断言(predicate)策略分组。这里不再举例说明。

3.6 counting

该方法归纳元素的的数量,非常简单,不再举例说明。

3.7 maxby/minby

这两个方法分别提供了查找大小元素的操作,它们基于比较器接口 comparator 来比较 ,返回的是一个 optional 对象。 我们来获取 servers 中最小长度的元素:

 // jetty  
optional<string> min = servers.stream.collect(collectors.minby(comparator.comparingint(string::length)));

这里其实 resin 长度也是最小,这里遵循了 "先入为主" 的原则 。当然 stream.min() 可以很方便的获取最小长度的元素。maxby 同样的道理。

3.8 summingint/double/long

用来做累加计算。计算元素某个属性的总和,类似 mysqlsum 函数,比如计算各个项目的盈利总和、计算本月的全部工资总和等等。我们这里就计算一下 servers 中字符串的长度之和 (为了举例不考虑其它写法)。

 // 总长度 32 
 servers.stream.collect(collectors.summingint(s -> s.length()));

3.9 summarizingint/double/long

如果我们对 3.6章节-3.8章节 的操作结果都要怎么办?难不成我们搞5个 stream 流吗? 所以就有了 summarizingintsummarizingdoublesummarizinglong 三个方法。
这三个方法通过对元素某个属性的提取,会返回对元素该属性的统计数据对象,分别对应 intsummarystatisticsdoublesummarystatisticslongsummarystatistics。我们对 servers 中元素的长度进行统计:

 doublesummarystatistics doublesummarystatistics = servers.stream.collect(collectors.summarizingdouble(string::length));
  // {count=5, sum=32.000000, min=5.000000, average=6.400000, max=8.000000}
  system.out.println("doublesummarystatistics.tostring() = " + doublesummarystatistics.tostring());

结果 doublesummarystatistics 中包含了 总数,总和,最小值,最大值,平均值 五个指标。

3.10 mapping

该方法是先对元素使用 function 进行再加工操作,然后用另一个collector 归纳。比如我们先去掉 servers 中元素的首字母,然后将它们装入 list

 // [elordcn, omcat, etty, ndertow, esin]
 servers.stream.collect(collectors.mapping(s -> s.substring(1), collectors.tolist()));

有点类似 stream 先进行了 map 操作再进行 collect

 servers.stream.map(s -> s.substring(1)).collect(collectors.tolist());

3.11 reducing

这个方法非常有用!但是如果要了解这个就必须了解其参数 binaryoperator<t> 。 这是一个函数式接口,是给两个相同类型的量,返回一个跟这两个量相同类型的一个结果,伪表达式为 (t,t) -> t。默认给了两个实现 maxbyminby ,根据比较器来比较大小并分别返回最大值或者最小值。当然你可以灵活定制。然后 reducing 就很好理解了,元素两两之间进行比较根据策略淘汰一个,随着轮次的进行元素个数就是 reduce 的。那这个有什么用处呢? java 官方给了一个例子:统计每个城市个子最高的人。

  comparator<person> byheight = comparator.comparing(person::getheight);
     map<string, optional<person>> tallestbycity = people.stream()
                          .collect(collectors.groupingby(person::getcity, collectors.reducing(binaryoperator.maxby(byheight))));

结合最开始给的例子你可以使用 reducing 找出最长的字符串试试。

上面这一层是根据 height 属性找最高的 person ,而且如果这个属性没有初始化值或者没有数据,很有可能拿不到结果所以给出的是 optional<person>。 如果我们给出了 identity 作一个基准值,那么我们首先会跟这个基准值进行 binaryoperator 操作。
比如我们给出高于 2 米 的人作为 identity。 我们就可以统计每个城市不低于 2 米 而且最高的那个人,当然如果该城市没有人高于 2 米则返回基准值identity

 comparator<person> byheight = comparator.comparing(person::getheight);
 person identity= new person();
           identity.setheight(2.);
           identity.setname("identity");
     map<string, person> collect = persons.stream()
                        .collect(collectors.groupingby(person::getcity, collectors.reducing(identity, binaryoperator.maxby(byheight))));

这时候就确定一定会返回一个 person 了,最起码会是基准值identity 不再是 optional

还有些情况,我们想在 reducing 的时候把 person 的身高先四舍五入一下。这就需要我们做一个映射处理。定义一个 function<? super t, ? extends u> mapper 来干这个活。那么上面的逻辑就可以变更为:

   comparator<person> byheight = comparator.comparing(person::getheight);
        person identity = new person();
        identity.setheight(2.);
        identity.setname("identity");
        // 定义映射 处理 四舍五入
        function<person, person> mapper = ps -> {
            double height = ps.getheight();

            bigdecimal decimal = new bigdecimal(height);
            double d = decimal.setscale(1, bigdecimal.round_half_up).doublevalue();
            ps.setheight(d);
            return ps;
        };
        map<string, person> collect = persons.stream()
                .collect(collectors.groupingby(person::getcity, collectors.reducing(identity, mapper, binaryoperator.maxby(byheight))));

4. 总结

今天我们对 java 8 中的 collectors 进行了详细的讲解。如果你熟悉了 collectors 操作 stream 会更加得心应手。当然在 java 8 之后的 java 9java 12collectors 都有新增的功能, 后面有时间我们会继续进行讲解。敬请关注!

如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复

相关文章:

验证码:
移动技术网