Java8 stream 中利用 groupingBy 进行多字段分组求和案例

Java8的groupingBy实现集合的分组,类似Mysql的group by分组功能,注意得到的是一个map

对集合按照单个属性分组、分组计数、排序

List<String> items =
    Arrays.asList("apple", "apple", "banana",
        "apple", "orange", "banana", "papaya");

// 分组
Map<String, List<String>> result1 = items.stream().collect(
    Collectors.groupingBy(
        Function.identity()
    )
);
//{papaya=[papaya], orange=[orange], banana=[banana, banana], apple=[apple, apple, apple]}
System.out.println(result1);

// 分组计数
Map<String, Long> result2 = items.stream().collect(
    Collectors.groupingBy(
        Function.identity(), Collectors.counting()
    )
);
// {papaya=1, orange=1, banana=2, apple=3}
System.out.println(result2);
Map<String, Long> finalMap = new LinkedHashMap<>();

//分组, 计数和排序
result2.entrySet().stream()
    .sorted(Map.Entry.<String, Long>comparingByValue().reversed())
    .forEachOrdered(e -> finalMap.put(e.getKey(), e.getValue()));
// {apple=3, banana=2, papaya=1, orange=1}
System.out.println(finalMap);

集合按照多个属性分组

1.多个属性拼接出一个组合属性

public static void main(String[] args) {
  User user1 = new User("zhangsan", "beijing", 10);
  User user2 = new User("zhangsan", "beijing", 20);
  User user3 = new User("lisi", "shanghai", 30);
  List<User> list = new ArrayList<User>();
  list.add(user1);
  list.add(user2);
  list.add(user3);
  Map<String, List<User>> collect = list.stream().collect(Collectors.groupingBy(e -> fetchGroupKey(e)));
  //{zhangsan#beijing=[User{age=10, name='zhangsan', address='beijing'}, User{age=20, name='zhangsan', address='beijing'}],
  // lisi#shanghai=[User{age=30, name='lisi', address='shanghai'}]}
  System.out.println(collect);
} 

private static String fetchGroupKey(User user){
  return user.getName() +"#"+ user.getAddress();
}

2.嵌套调用groupBy

User user1 = new User("zhangsan", "beijing", 10);
User user2 = new User("zhangsan", "beijing", 20);
User user3 = new User("lisi", "shanghai", 30);
List<User> list = new ArrayList<User>();
list.add(user1);
list.add(user2);
list.add(user3);
Map<String, Map<String, List<User>>> collect
    = list.stream().collect(
        Collectors.groupingBy(
            User::getAddress, Collectors.groupingBy(User::getName)
        )
);
System.out.println(collect);

3. 使用Arrays.asList

我有一个与Web访问记录相关的域对象列表。这些域对象可以扩展到数千个。

我没有资源或需求将它们以原始格式存储在数据库中,因此我希望预先计算聚合并将聚合的数据放在数据库中。

我需要聚合在5分钟窗口中传输的总字节数,如下面的sql查询

select
 round(request_timestamp, '5') as window, --round timestamp to the nearest 5 minute
 cdn,
 isp,
 http_result_code,
 transaction_time,
 sum(bytes_transferred)
from web_records
group by
  round(request_timestamp, '5'),
  cdn,
  isp,
  http_result_code,
  transaction_time

在java 8中,我当前的第一次尝试是这样的,我知道这个解决方案类似于Group by multiple field names in java 8

Map<Date, Map<String, Map<String, Map<String, Map<String, Integer>>>>>>> aggregatedData =
webRecords
  .stream()
  .collect(Collectors.groupingBy(WebRecord::getFiveMinuteWindow,
        Collectors.groupingBy(WebRecord::getCdn,
         Collectors.groupingBy(WebRecord::getIsp,
          Collectors.groupingBy(WebRecord::getResultCode,
            Collectors.groupingBy(WebRecord::getTxnTime,
             Collectors.reducing(0,
                       WebRecord::getReqBytes(),
                       Integer::sum)))))));

这是可行的,但它是丑陋的,所有这些嵌套的地图是一个噩梦!要将地图“展平”或“展开”成行,我必须这样做

for (Date window : aggregatedData.keySet()) {
 for (String cdn : aggregatedData.get(window).keySet()) {
  for (String isp : aggregatedData.get(window).get(cdn).keySet()) {
   for (String resultCode : aggregatedData.get(window).get(cdn).get(isp).keySet()) {
    for (String txnTime : aggregatedData.get(window).get(cdn).get(isp).get(resultCode).keySet()) {

      Integer bytesTransferred = aggregatedData.get(window).get(cdn).get(distId).get(isp).get(resultCode).get(txnTime);
      AggregatedRow row = new AggregatedRow(window, cdn, distId...

如你所见,这是相当混乱和难以维持。

有谁知道更好的方法吗?任何帮助都将不胜感激。

我想知道是否有更好的方法来展开嵌套的映射,或者是否有一个库允许您对集合进行分组。

最佳答案

您应该为地图创建自定义密钥。最简单的方法是使用Arrays.asList:

Function<WebRecord, List<Object>> keyExtractor = wr ->
  Arrays.<Object>asList(wr.getFiveMinuteWindow(), wr.getCdn(), wr.getIsp(),
       wr.getResultCode(), wr.getTxnTime());

Map<List<Object>, Integer> aggregatedData = webRecords.stream().collect(
   Collectors.groupingBy(keyExtractor, Collectors.summingInt(WebRecord::getReqBytes)));

在这种情况下,键是按固定顺序列出的5个元素。不是很面向对象,但很简单。或者,您可以定义自己的表示自定义键的类型,并创建适当的hashCode/equals实现。

补充知识:java8 新特性 Stream流 分组 排序 过滤 多条件去重 (最小、最大、平均、求和)

什么是 Stream?

Stream 是用函数式编程方式在集合类上进行复杂操作的工具,其集成了Java 8中的众多新特性之一的聚合操作,开发者可以更容易地使用Lambda表达式,并且更方便地实现对集合的查找、遍历、过滤以及常见计算等。话不多说,直接上代码。

List<User> list = new ArrayList<User>();
list = Arrays.asList(
    new User("小强", 11, "男"),
    new User("小玲", 15, "女"),
    new User("小虎", 23, "男"),
    new User("小雨", 26, "女"),
    new User("小飞", 19, "男"),
    new User("小玲", 15, "女")
);
//分组
Map<String, List<User>> listMap = list.stream().collect(Collectors.groupingBy(User::getSex));
for(String key:listMap.keySet()){
  System.out.print(key+"组:");
  listMap.get(key).forEach(user -> System.out.print(user.getName()));
  System.out.println();
}
//排序
list.stream().sorted(Comparator.comparing(user-> user.getAge()))
    .forEach(user -> System.out.println(user.getName()));
//过滤
list.stream().filter(user -> user.getSex().equals("男")).collect(Collectors.toList())
    .forEach(user -> System.out.println(user.getName()));
//多条件去重
list.stream().collect(Collectors.collectingAndThen(
    Collectors.toCollection(() -> new TreeSet<>(
        Comparator.comparing(user -> user.getAge() + ";" + user.getName()))), ArrayList::new))
    .forEach(user -> System.out.println(user.getName()));
//最小值
Integer min = list.stream().mapToInt(User::getAge).min().getAsInt();
//最大值
Integer max = list.stream().mapToInt(User::getAge).max().getAsInt();
//平均值
Double average = list.stream().mapToInt(User::getAge).average().getAsDouble();
//和
Integer sum = list.stream().mapToInt(User::getAge).sum();
System.out.println("最小值:"+min+", 最大值"+max+", 平均值:"+average+", 和:"+sum);
//分组求和
Map<String, IntSummaryStatistics> collect = list.stream().collect(Collectors.groupingBy(User::getSex, Collectors.summarizingInt(User::getAge)));
IntSummaryStatistics statistics1 = collect.get("男");
IntSummaryStatistics statistics2 = collect.get("女");
System.out.println(statistics1.getSum());
System.out.println(statistics1.getAverage());
System.out.println(statistics1.getMax());
System.out.println(statistics1.getMin());
System.out.println(statistics1.getCount());
System.out.println(statistics2.getSum());
System.out.println(statistics2.getAverage());
System.out.println(statistics2.getMax());
System.out.println(statistics2.getMin());
System.out.println(statistics2.getCount());
//提取list中两个属性值,转为map
Map<String, String> userMap = list.stream().collect(Collectors.toMap(User::getName, User::getSex));
System.out.println(JsonUtil.toJson(userMap))
//取出所有名字
List<String> names = list.stream().map(User::getName).collect(Collectors.toList());
System.out.println(JsonUtil.toJson(names))

以上这篇Java8 stream 中利用 groupingBy 进行多字段分组求和案例就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持我们。

时间: 2020-08-24

java8 集合 多字段 分组 统计个数代码

1.user实体 package com.demo.dto; public class User { private Integer id; private String userName; private String password; private Integer age; private long c; public User() { super(); // TODO Auto-generated constructor stub } public User(Integer id, S

JAVA JDK8 List分组的实现和用法

概述 对List进行分组是日常开发中,经常遇到的,在JDK 8中对List按照某个属性分组的代码,超级简单. package test; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.serializer.SerializerFeature; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util

JAVA8 stream中三个参数的reduce方法对List进行分组统计操作

背景 平时在编写前端代码时,习惯使用lodash来编写'野生'的JavaScript; lodash提供来一套完整的API对JS对象(Array,Object,Collection等)进行操作,这其中就包括_.groupBy 和 _.reduce,即分组和'聚合'(reduce不知道该怎么翻译合适). 使用这些'野生'的API能够极大的提高我本人编写JS代码的效率.而JAVA8开始支持stream和lambda表达式,这些和lodash的API有很多类似的功能.因此我在熟悉lodash的前提下尝

java8新特性 stream流的方式遍历集合和数组操作

前言: 在没有接触java8的时候,我们遍历一个集合都是用循环的方式,从第一条数据遍历到最后一条数据,现在思考一个问题,为什么要使用循环,因为要进行遍历,但是遍历不是唯一的方式,遍历是指每一个元素逐一进行处理(目的),而并不是从第一个到最后一个顺次处理的循环,前者是目的,后者是方式. 所以为了让遍历的方式更加优雅,出现了流(stream)! 1.流的目的在于强掉做什么 假设一个案例:将集合A根据条件1过滤为子集B,然后根据条件2过滤为子集C 在没有引入流之前我们的做法可能为: public cl

Java8新特性Stream流实例详解

什么是Stream流? Stream流是数据渠道,用于操作数据源(集合.数组等)所生成的元素序列. Stream的优点:声明性,可复合,可并行.这三个特性使得stream操作更简洁,更灵活,更高效. Stream的操作有两个特点:可以多个操作链接起来运行,内部迭代. Stream可分为并行流与串行流,Stream API 可以声明性地通过 parallel() 与sequential() 在并行流与顺序流之间进行切换.串行流就不必再细说了,并行流主要是为了为了适应目前多核机器的时代,提高系统CP

Java8新特性Stream的完全使用指南

什么是Stream Stream是Java 1.8版本开始提供的一个接口,主要提供对数据集合使用流的方式进行操作,流中的元素不可变且只会被消费一次,所有方法都设计成支持链式调用.使用Stream API可以极大生产力,写出高效率.干净.简洁的代码. 如何获得Stream实例 Stream提供了静态构建方法,可以基于不同的参数创建返回Stream实例 使用Collection的子类实例调用stream()或者parallelStream()方法也可以得到Stream实例,两个方法的区别在于后续执行

Java8新特性Stream短路终端操作实例解析

这篇文章主要介绍了Java8新特性Stream短路终端操作实例解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 传入一个谓词,返回传为boolean,如果符合条件,则直接结束流. 匹配所有 allMatch 任意匹配 anymMatch 不匹配 noneMatch 查找首个 findFirst 查找任意 findAny 匹配所有 allMatch /匹配所有 allMatch @Test public void allMatchTest()

JDK1.8新特性Stream流式操作的具体使用

一. 前言 随着Java的发展,越来越多的企业开始使用JDK1.8 版本.JDK1.8 是自 JDK1.5之后最重要的版本,这个版本包含语言.编译器.库.工具.JVM等方面的十多个新特性.本次文章将着重学习Stream. Stream 是JDK1.8 中处理集合的关键抽象概念,Lambda 和 Stream 是JDK1.8新增的函数式编程最有亮点的特性了,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找.过滤和映射数据等操作.使用Stream API 对集合数据进行操作,就类似于使用SQ

详解Java8新特性Stream之list转map及问题解决

List集合转Map,用到的是Stream中Collectors的toMap方法:Collectors.toMap 具体用法实例如下: //声明一个List集合 List<Person> list = new ArrayList(); list.add(new Person("1001", "小A")); list.add(new Person("1002", "小B")); list.add(new Person

java8新特性之stream的collect实战教程

1.list转换成list 不带return方式 List<Long> ids=wrongTmpList.stream().map(c->c.getId()).collect(Collectors.toList()); 带return方式 // spu集合转化成spubo集合//java8的新特性 List<SpuBo> spuBos=spuList.stream().map(spu -> { SpuBo spuBo = new SpuBo(); BeanUtils.c

Java8 新特性Lambda表达式实例详解

Java8 新特性Lambda表达式实例详解 在介绍Lambda表达式之前,我们先来看只有单个方法的Interface(通常我们称之为回调接口): public interface OnClickListener { void onClick(View v); } 我们是这样使用它的: button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { v.setText("

java8新特性将List中按指定属性排序过滤重复数据的方法

在java中常常会遇到这样一个问题,在实际应用中,总会碰到对List排序并过滤重复的问题,如果List中放的只是简单的String类型过滤so easy,但是实际应用中并不会这么easy,往往List中放的是一个类,类中有多个属性,要过滤重复数据,而且这个重复数据要按自己指定的属性过滤,但是要想按照其它属性排序顺序过滤,所以要先排序一下,然后按照某个属性过滤. 实体类如下所示,大家只要创建下面的实体类,无需继承父类,大家不会注解式风格的话,请自行加上getter/setter方法. 首先看看gr

Java8新特性之空指针异常的克星Optional类的实现

Java8新特性系列我们已经介绍了Stream.Lambda表达式.DateTime日期时间处理,最后以"NullPointerException" 的克星Optional类的讲解来收尾. 背景 作为开发人员每天与NullPointerException斗智斗勇.每接收到参数或调用方法获得值得判断一下是否为null.稍不留意,空指针异常就像幽灵一样出现了. 这篇文章我们来学习Java8是如何通过Optional类来避免空指针异常的. 先来看一下不使用Optional类时,我们为了防止N