为了账号安全,请及时绑定邮箱和手机立即绑定

Java 8 Streams API:对Stream分组和分区

标签:
Java

原文链接

这篇文章展示了如何使用 Streams API 中的 Collector 及 groupingBy 和 partitioningBy 来对流中的元素进行分组和分区。

思考一下 Employee 对象流,每个对象对应一个名字、城市和销售数量,如下表所示:

+----------+------------+-----------------+| Name     | City       | Number of Sales |+----------+------------+-----------------+| Alice    | London     | 200             || Bob      | London     | 150             || Charles  | New York   | 160             || Dorothy  | Hong Kong  | 190             |+----------+------------+-----------------+

分组

首先,我们利用(lambda表达式出现之前的)命令式风格Java 程序对流中的雇员按城市进行分组:

Map<String, List<Employee>> result = new HashMap<>();for (Employee e : employees) {  String city = e.getCity();
  List<Employee> empsInCity = result.get(city);  if (empsInCity == null) {
    empsInCity = new ArrayList<>();
    result.put(city, empsInCity);
  }
  empsInCity.add(e);
}

你可能很熟悉写这样的代码,你也看到了,一个如此简单的任务就需要这么多代码!

而在 Java 8 中,你可以使用 groupingBy 收集器,一条语句就能完成相同的功能,像这样:

Map<String, List<Employee>> employeesByCity =
  employees.stream().collect(groupingBy(Employee::getCity));

结果如下面的 map 所示:

{New York=[Charles], Hong Kong=[Dorothy], London=[Alice, Bob]}

还可以计算每个城市中雇员的数量,只需传递一个计数收集器给 groupingBy 收集器。第二个收集器的作用是在流分类的同一个组中对每个元素进行递归操作。

Map<String, Long> numEmployeesByCity =
  employees.stream().collect(groupingBy(Employee::getCity, counting()));

结果如下面的 map 所示:

{New York=1, Hong Kong=1, London=2}

顺便提一下,该功能与下面的 SQL 语句是等同的:

select city, count(*) from Employee group by city

另一个例子是计算每个城市的平均年龄,这可以联合使用 averagingInt 和 groupingBy 收集器:

Map<String, Double> avgSalesByCity =
  employees.stream().collect(groupingBy(Employee::getCity,
                               averagingInt(Employee::getNumSales)));

结果如下 map 所示:

{New York=160.0, Hong Kong=190.0, London=175.0}

分区

分区是一种特殊的分组,结果 map 至少包含两个不同的分组——一个true,一个false。例如,如果想找出最优秀的员工,你可以将所有雇员分为两组,一组销售量大于 N,另一组小于 N,使用 partitioningBy 收集器:

Map<Boolean, List<Employee>> partitioned =
  employees.stream().collect(partitioningBy(e -> e.getNumSales() > 150));

输出如下结果:

{false=[Bob], true=[Alice, Charles, Dorothy]}

你也可以将 groupingBy 收集器传递给 partitioningBy 收集器来将联合使用分区和分组。例如,你可以统计每个分区中的每个城市的雇员人数:

Map<Boolean, Map<String, Long>> result =
  employees.stream().collect(partitioningBy(e -> e.getNumSales() > 150,
                               groupingBy(Employee::getCity, counting())));

这样会生成一个二级 Map:

{false={London=1}, true={New York=1, Hong Kong=1, London=1}}


点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消