使用Java 8 Stream像操作SQL一样处理数据(下)

在上一篇文章中,我们介绍了Stream可以像操作数据库一样来操作集合,但是我们没有介绍flatMapcollect操作。

这两种操作对实现复杂的查询是非常有用的。比如你可以结果和计算stream中的单词的字符数,像下面代码那样。

上述代码的运行结果是:

这篇文章将会介绍flatMap和collect这两种操作的更多细节。

flatMap操作

假设你在一个文章中查找一个单词,你会怎么做?

我们可以使用方法,因为它可以返回一个文章一行一行信息组成的stream。我们可以使用把文章的每行分割是很多单词,最后,使用`distinct()``移除重复的。我们将想法转化为代码:

很不幸,这样并不正确。如果你运行得到这样的结果:

到底发生了什么事呢?问题出在使用的lambda表达式将会把文件的每行转化成一个字符串数组(String[])。这就导致map返回的是一个Stream类型的结果,我们实际上需要的是一个Stream类型的结果。

我们需要一串的单词,而不是一串的数组。对于数组可以使用将数组变成一个stream。看下面的实现:

如果我们使用下面方式的话其实还有不起作用的,这是因为使用后返回的其实是Stream类型。

我们可以使用flatMap来解决这种问题,像下面这样。使用flatMap方法的作用是返回的是stream中的内容而不是一个stream。

collect 操作

我们来具体看一下collect操作。上面文章中看到了返回stream的操作(说明该操作是一个中间操作)和返回一个值、boolean型值、int型值和Optional型值的操作(说明该操作是终结操作)

将Stream中的元素转化到集合中

使用toSet()你可以把一个stream转化成一个不包含重复项的集合。下面的代码展示了怎么生成高消费(单笔交易>1000$)城市的集合。

注意这样你不能保证返回什么类型的Set,你可以使用toCollection()来提高可控性。比如你可以像下面代码这样将一个HashSet的构造方法作为参数。

collect操作方法不止这些,上面介绍的只是很小一部分,还可以实现这些功能:

通过货币类型进行分组,计算各种获取类型的交易总金额(将会返回一个 Map)

将所有交易分类两组:大金额的和非大金额的(将会返回一个Map

创建多级分组,比如先根据城市分组,然后再根据是否为大金额交易分组( 将会返回一个Map

让我们看一下Stream API和集合器怎么实现这些查询,我们先对一个stream中的数据进行计算平均值,最大值和最小值。接下来我们再看如果实现简单的分组,最后我们我们将多个集合器放在一起实现强大的查询功能,比如多级分组。

Summarizing

有很多预定义的集合器和是很方便的使用,比如使用counting() 计算个数:

你可以对Double, Int, 或者Long属性的元素进行 summing Double(), summingInt(), and summingLong() 操作,像下面这样:

类似的你还可以使用averagingDouble(), averagingInt(), and averagingLong() 计算平均值,像下面这样:

还可以通过使用maxBy()和minBy()计算元素中的最大值和最小值,不过你需要定义一个做比较的 比较器,所以maxBy和minBy需要一个Comparator对象最为参数:

下面的例子中我们使用了静态方法comparing(),它将根据传递进去的参数生成一个Comparator对象。这个方法根据提取stream中元素的可以做比较的key来做判断。在这个例子中是通过银行交易的金额大小来做比较的。

还有一个叫reducing()的集合器,它可以通过重复地对stream中的所有元素进行一种操作指导产生一个结果。它和reduce()有点类似。比如下面的代码使用reducing()方法计算交易的总金额。

reducing() 有三个参数:

初始值(如果stream是空也将返回该值):这里是0

一个会被应用到各个元素的方法

结合两个提取出来的值,这是是将两个值加起来

Grouping

一个常规的数据库操作就是根据一个属性对数据进行分组。比如根据货币对交易进行分组,如果使用迭代那简直太复杂了:

Java 8 中有一个叫的集合器,我们可以像这样做查询:

groupingBy() 方法有一个提取分类key的函数做参数,我们可以叫它为分类函数。在这个例子中我们使用的是Transaction::getCurrency来实现根据货币分组。

Partitioning

还有一个叫做partitioningBy()的函数,这个可以看做是groupingBy()的特例。它需要一个predicate(返回一个boolean的函数)作为参数,将会对stream中的元素根据是否满足predicate进行分类。partitioning可以将stream变成一个 Map

如果要要对不同货币的金额进行求和操作在SQL中可以结合使用SUM和GROUP BY。那我们使用Stream API也能这么做吗?当然可以了,像下面这样使用:

之前使用的groupingBy (Transaction::getCity)其实是groupingBy (Transaction::getCity, toList())的速写方式。

再看一个例子,如果你要统计每个城市的交易最大值,可以做这样实现:

再看一个更加复杂的例子,在刚才的例子中我们给groupingBy传递了另外一个集合器作为参数来进一步对元素进行分组。由于groupingBy本身是一个集合器,我们可以通过传递其他groupingBy集合器来创建多级分组,被传递进来的这个groupingBy定义了一个二级标准可以对stream中的元素进行再分组。

下面代码中我们先对城市进行分组,然后我们再根据每个城市的交易不同货币的平均值进行分组

自定义集合器

我们看到的这些集合器都实现了接口。这就意味着你可以自定义集合器。

总结

这篇文章中,我们探索了两个Stream API的高级操作:flatMap和colelct。通过这两个操作你可以创建更加复杂的数据处理查询。

我们还通过collect方法实现了summarizing, grouping, 和 partitioning 操作。这些操作还可以被结合起来创建更加复杂的查询。

最后

  • 发表于:
  • 原文链接http://kuaibao.qq.com/s/20180119G0YAE100?refer=cp_1026
  • 腾讯「云+社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。

扫码关注云+社区

领取腾讯云代金券