Java stream多字段分组(groupingBy)

news2024/11/25 21:53:14

近期的项目里,遇到一个需求:对于含有多个元素的List<Person>,按照其中的某几个属性进行分组,比如Persion::getAgePersion::getTypePersion::getGender等字段。下面就让我们讨论一下如何比较优雅的按多字段进行分组groupingBy。

利用Stream进行分组

Stream是Java8的一个新特性,主要用户集合数据的处理,如排序、过滤、去重等等功能,这里我们不展开讲解。本文主要讲解的是利用Stream.collect()来对List进行分组。
Person类Person.java:

public class Person {

    /**
     * id
     */
    private Integer id;

    /**
     * 年龄
     */
    private Integer age;

    /**
     * 类型
     */
    private String type;

    /**
     * 姓名
     */
    private String name;

    /**
     * 性别
     */
    private String gender;

    public Integer getId() {
        return id;
    }

    public Person setId(Integer id) {
        this.id = id;
        return this;
    }

    public Integer getAge() {
        return age;
    }

    public Person setAge(Integer age) {
        this.age = age;
        return this;
    }

    public String getType() {
        return type;
    }

    public Person setType(String type) {
        this.type = type;
        return this;
    }

    public String getName() {
        return name;
    }

    public Person setName(String name) {
        this.name = name;
        return this;
    }

    public String getGender() {
        return gender;
    }

    public Person setGender(String gender) {
        this.gender = gender;
        return this;
    }
}

1. 利用单个字段进行分组

如上面的Person类,如果对于其中的某一个字段进行分组(如gender),则比较简单,我们可以利用Stream.collect()Collectors.groupingBy结合,即可进行分组groupingBy,代码如下:

public class TestGroupingBy {

    public static void main(String[] args) {

        List<Person> personList = Arrays.asList(
                new Person().setId(1).setAge(18).setType("student").setName("user - 1").setGender("male"),
                new Person().setId(2).setAge(20).setType("student").setName("user - 2").setGender("male"),
                new Person().setId(3).setAge(18).setType("student").setName("user - 3").setGender("male"),
                new Person().setId(4).setAge(18).setType("student").setName("user - 4").setGender("male"),
                new Person().setId(5).setAge(35).setType("teacher").setName("user - 5").setGender("male"),
                new Person().setId(6).setAge(35).setType("teacher").setName("user - 6").setGender("male"),
                new Person().setId(7).setAge(20).setType("student").setName("user - 7").setGender("male"),
                new Person().setId(8).setAge(20).setType("student").setName("user - 8").setGender("female"),
                new Person().setId(9).setAge(20).setType("student").setName("user - 9").setGender("female"),
                new Person().setId(10).setAge(20).setType("student").setName("user - 10").setGender("female")
        );

        Map<String, List<Person>> groupingMap = personList.stream().collect(Collectors.groupingBy(Person::getGender));
}

其中的groupingMap ,类型为Map<String, List<Person>>,第一个泛型为String即分组字段(本例中为gender字段)的类型,第二个泛型为List<Person>及分组结果的类型。
我们在Debug模式下运行代码,可以看到groupingMap 数据如下:
单个字段分组结果
可以看到personList数据按照gender属性被分成了两组。

2. 利用多个字段进行分组

上面的例子是按单个字段分组,如果需要按照多个字段,如gender、age、type三个字段进行分组,同样也可以可以利用Stream.collect()Collectors.groupingBy结合的方式进行分组,不过该方式中调用Collectors.groupingBy时需要多次嵌套调用,测试代码如下:

public class TestGroupingBy {

    public static void main(String[] args) {

        List<Person> personList = Arrays.asList(
                new Person().setId(1).setAge(18).setType("student").setName("user - 1").setGender("male"),
                new Person().setId(2).setAge(20).setType("student").setName("user - 2").setGender("male"),
                new Person().setId(3).setAge(18).setType("student").setName("user - 3").setGender("male"),
                new Person().setId(4).setAge(18).setType("student").setName("user - 4").setGender("male"),
                new Person().setId(5).setAge(35).setType("teacher").setName("user - 5").setGender("male"),
                new Person().setId(6).setAge(35).setType("teacher").setName("user - 6").setGender("male"),
                new Person().setId(7).setAge(20).setType("student").setName("user - 7").setGender("male"),
                new Person().setId(8).setAge(20).setType("student").setName("user - 8").setGender("female"),
                new Person().setId(9).setAge(20).setType("student").setName("user - 9").setGender("female"),
                new Person().setId(10).setAge(20).setType("student").setName("user - 10").setGender("female")
        );

        // 多字段嵌套分组
        Map<String, Map<Integer, Map<String, List<Person>>>> groupingMap = personList.stream().collect(
                Collectors.groupingBy(Person::getGender, 
                        Collectors.groupingBy(Person::getAge, 
                                Collectors.groupingBy(Person::getType)
                        )
                )
        );

    }
}

其中groupingMap类型为Map<String, Map<Integer, Map<String, List<Person>>>>,是一个嵌套了三层的Map,对应的泛型String/Integer/String分别为对应分组字段的类型,最后一层Map的value类型为List<Person>为实际分组后的数据集合类型,为方便查看数据,特意按Json格式贴出数据如下:

{
  "female": {
    "20": {
      "student": [
        {
          "id": 8,
          "age": 20,
          "type": "student",
          "name": "user - 8",
          "gender": "female"
        },
        {
          "id": 9,
          "age": 20,
          "type": "student",
          "name": "user - 9",
          "gender": "female"
        },
        {
          "id": 10,
          "age": 20,
          "type": "student",
          "name": "user - 10",
          "gender": "female"
        }
      ]
    }
  },
  "male": {
    "18": {
      "student": [
        {
          "id": 1,
          "age": 18,
          "type": "student",
          "name": "user - 1",
          "gender": "male"
        },
        {
          "id": 3,
          "age": 18,
          "type": "student",
          "name": "user - 3",
          "gender": "male"
        },
        {
          "id": 4,
          "age": 18,
          "type": "student",
          "name": "user - 4",
          "gender": "male"
        }
      ]
    },
    "20": {
      "student": [
        {
          "id": 2,
          "age": 20,
          "type": "student",
          "name": "user - 2",
          "gender": "male"
        },
        {
          "id": 7,
          "age": 20,
          "type": "student",
          "name": "user - 7",
          "gender": "male"
        }
      ]
    },
    "35": {
      "teacher": [
        {
          "id": 5,
          "age": 35,
          "type": "teacher",
          "name": "user - 5",
          "gender": "male"
        },
        {
          "id": 6,
          "age": 35,
          "type": "teacher",
          "name": "user - 6",
          "gender": "male"
        }
      ]
    }
  }
}

可以看到,原先的List数据,按照gender/age/type三个属性,分成了三层的Map,对于这种多层的Map代码上处理起来会有一些不方便。并且如果分组字段更多的话,所嵌套的Collectors.groupingBy也会更加多,代码书写起来也不太优雅。

下面将介绍另外一种按多字段分组的方法。

3. 利用Collectors.groupingBy与Function结合进行多字段分组

查看Collectors.groupingByAPI会发现,其中一种用法是第一个参数为Function,如下:
在这里插入图片描述
简单翻译一下就是:一种将输入元素映射到键的分类函数。即需要定义一个函数Function,该函数将元素对象映射到一个键的集合里。代码示例如下:

public class TestGroupingBy {

    public static void main(String[] args) {

        List<Person> personList = Arrays.asList(
                new Person().setId(1).setAge(18).setType("student").setName("user - 1").setGender("male"),
                new Person().setId(2).setAge(20).setType("student").setName("user - 2").setGender("male"),
                new Person().setId(3).setAge(18).setType("student").setName("user - 3").setGender("male"),
                new Person().setId(4).setAge(18).setType("student").setName("user - 4").setGender("male"),
                new Person().setId(5).setAge(35).setType("teacher").setName("user - 5").setGender("male"),
                new Person().setId(6).setAge(35).setType("teacher").setName("user - 6").setGender("male"),
                new Person().setId(7).setAge(20).setType("student").setName("user - 7").setGender("male"),
                new Person().setId(8).setAge(20).setType("student").setName("user - 8").setGender("female"),
                new Person().setId(9).setAge(20).setType("student").setName("user - 9").setGender("female"),
                new Person().setId(10).setAge(20).setType("student").setName("user - 10").setGender("female")
        );

        // 定义一个函数Function,该函数将元素对象映射到一个键的集合里
        Function<Person, List<Object>> compositeKey = person ->
                Arrays.asList(person.getGender(), person.getAge(), person.getType());

        // 分组
        Map<List<Object>, List<Person>> groupingMap =
                personList.stream().collect(Collectors.groupingBy(compositeKey, Collectors.toList()));

    }
}

通过在Debug模式下运行代码,可以看到groupingMap的数据结构如下:
在这里插入图片描述
groupingMap数据仅仅只有一层,但是其键值Key却是一个List,里面包含了分组字段的值,如上图中的male35teacher是集合中属性gender/age/type分别是male35teacher的元素集合。数据按Json格式贴出如下:

{
  "[male, 35, teacher]": [
    {
      "id": 5,
      "age": 35,
      "type": "teacher",
      "name": "user - 5",
      "gender": "male"
    },
    {
      "id": 6,
      "age": 35,
      "type": "teacher",
      "name": "user - 6",
      "gender": "male"
    }
  ],
  "[female, 20, student]": [
    {
      "id": 8,
      "age": 20,
      "type": "student",
      "name": "user - 8",
      "gender": "female"
    },
    {
      "id": 9,
      "age": 20,
      "type": "student",
      "name": "user - 9",
      "gender": "female"
    },
    {
      "id": 10,
      "age": 20,
      "type": "student",
      "name": "user - 10",
      "gender": "female"
    }
  ],
  "[male, 20, student]": [
    {
      "id": 2,
      "age": 20,
      "type": "student",
      "name": "user - 2",
      "gender": "male"
    },
    {
      "id": 7,
      "age": 20,
      "type": "student",
      "name": "user - 7",
      "gender": "male"
    }
  ],
  "[male, 18, student]": [
    {
      "id": 1,
      "age": 18,
      "type": "student",
      "name": "user - 1",
      "gender": "male"
    },
    {
      "id": 3,
      "age": 18,
      "type": "student",
      "name": "user - 3",
      "gender": "male"
    },
    {
      "id": 4,
      "age": 18,
      "type": "student",
      "name": "user - 4",
      "gender": "male"
    }
  ]
}

由于Map只有一层,用该方式分组的结果,对于我们业务也是比较友好,代码里对数据处理起来也是比较方便的。可以看到,从代码书写角度以及分组处理后得到的结果,该方法都是最优雅的。

写在最后

可以看到,如果分组字段只有一个,我们可以用比较简单的利用Stream.collect()Collectors.groupingBy进行处理,但对于多个字段的分组操作,建议还是用Collectors.groupingByFunction进行处理。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/621723.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

vue3 使用element plus 打包时 报错

vue3vitetselementPlus中运行正常打包出错 能正常运行&#xff0c;但是打包出错 解决打包时出现导入element plus相关的爆红&#xff0c;导致无法打包的问题 如若出现类似于&#xff1a;Module ‘“element-plus”’ has no exported member ‘ElMessage’. Did you mean to …

使用Fiddler进行Mock测试的原理和方法

叨叨在前&#xff1a;之前工作中&#xff0c;做的纯接口测试&#xff0c;当时公司有自己的Mock平台&#xff0c;可以进行接口Mock测试。后来离开公司后&#xff0c;再想做Mock测试&#xff0c;就只能使用开源工具&#xff0c;或者自己用Flask写Mock了。今儿整理一下&#xff0c…

二、视频换脸

介绍一个优秀的开源项目,DeepFaceLab。开发者是来自俄罗斯的Iperov,同时,他也是曾经应用最广泛的DeepFaceLab的开发者质之一。开源地址:https://github.com/iperov/DeepFaceLab 而这种高质量伪造图像的背后,依托的是“生成对抗网络”(GAN)技术。这类网络由两个 AI 代…

chatgpt赋能python:Python嵌入包:让你的程序更灵活

Python嵌入包&#xff1a;让你的程序更灵活 Python是一种动态、面向对象、高级编程语言&#xff0c;被广泛应用于科学计算、Web开发、大数据、人工智能等领域。Python提供了大量的标准库和第三方库&#xff0c;让开发者能够快速、高效地实现各种功能需求。 但是&#xff0c;在…

ROS EKF 机器人位姿估计功能包:robot_pose_ekf 安装(Ubuntu20.04)

ROS EKF 机器人位姿估计功能包&#xff1a;robot_pose_ekf 安装&#xff08;Ubuntu20.04&#xff09; 功能包简介安装 功能包简介 robot_pose_ekf软件包用于基于来自不同来源的&#xff08;部分&#xff09;位姿测量值来估计机器人的3D姿势。它使用带有6D模型&#xff08;3D位…

想要精通算法和SQL的成长之路 - 两两交换链表中的节点

想要精通算法和SQL的成长之路 - 两两交换链表中的节点 前言一. 两两交换链表中的节点 前言 想要精通算法和SQL的成长之路 - 系列导航 一. 两两交换链表中的节点 原题链接 给你一个链表&#xff0c;两两交换其中相邻的节点&#xff0c;并返回交换后链表的头节点。你必须在不修改…

1.8w 字的 SQL 优化大全

目录 1、MySQL的基本架构 1&#xff09;MySQL的基础架构图 2&#xff09;查询数据库的引擎 ① show engines; ② show variables like “%storage_engine%”; 3&#xff09;指定数据库对象的存储引擎 2. SQL优化 1&#xff09;为什么需要进行SQL优化&#xff1f; 2&am…

迷你版ChatGPT开源,教你怎么用nanoGPT训练一个写小说的AI机器人!

大家好,我是千与千寻,好久不见,最近太忙了,去医院拔了颗智齿,这不是刚休息一天,就立刻来给大家分享ChatGPT的新奇项目了。 ChatGPT的功能确实是好用,但是我觉得有一个小缺点,就是反应的时间比较慢,原因是GPT-3.5/GPT-4.0的模型体积较大,比较占用内存空间。 同时大模…

MySQL存储引擎介绍、InnoDB和MyISAM的区别

存储引擎是MySQL的核心组件&#xff0c;是存储数据、建立索引、更新/查询数据等技术的实现方式&#xff0c;存储引擎是基于表的&#xff0c;而不是基于库的&#xff0c;所以存储引擎也可被称为表类型。以在创建表的时候&#xff0c;来可以指定选择的存储引擎&#xff0c;如果没…

指针--指针变量的定义和初始化

存放变量的地址需要一种特殊类型的变量&#xff0c;这种特殊的数据类型就是指针&#xff08;Pointer&#xff09;。 具有指针类型的变量&#xff0c;称为指针变量&#xff0c;它时专门用于存储变量的地址值和变量。 其定义形式如下&#xff1a; 类型关键字 * 指针变量名&#x…

【Swift基础语法SnapKit自动布局库的使用】

文章目录 前言playgroundvar 和 let-元组string-字典-数组闭包enum类和对象属性类UI和OC的区别&#xff0c;更简洁懒加载全局文件snap kit的使用top 和 topMargin 总结 前言 最近在学习swift和写项目&#xff0c;给我的感受&#xff0c;语言简洁和安全&#xff0c;在学习了基础…

从GitHub上新拉一个项目并在mac系统下运行遇到的种种问题

如果项目已经被拉下来&#xff0c;现在我们要启动项目。 step1、需要了解该项目是什么&#xff0c;它的作用是什么&#xff0c;以及它可以解决的问题。阅读项目的介绍、文档和示例&#xff0c;确保对项目有一个基本的认识。 step2、设置开发环境&#xff1a;确保您的开发环境…

网络安全系统教程+渗透测试+学习路线(自学笔记)

一、什么是网络安全 网络安全可以基于攻击和防御视角来分类&#xff0c;我们经常听到的 “红队”、“渗透测试” 等就是研究攻击技术&#xff0c;而“蓝队”、“安全运营”、“安全运维”则研究防御技术。 无论网络、Web、移动、桌面、云等哪个领域&#xff0c;都有攻与防两面…

使用Smart Install Maker为应用制作安装程序

一. 前言 Unity发布发布PC平台exe的时候&#xff0c;是生成一个Data目录和一个exe文件&#xff0c;如下&#xff1a; 而一般软件发布的时候&#xff0c;是只有一个Setup.exe这样子的安装程序&#xff0c;可以使用Smart Install Maker这个软件来制作这个Setup.exe程序。 二. S…

函数重载与缺省参数

目录 一 缺省参数 缺省参数分半缺省和全缺省。 2&#xff0c;半缺省参数 3&#xff0c;全缺省参数 4.缺省参数的注意事项 二 函数重载 2 .函数重载参数类型不同强调 三 函数名修饰规则 一 缺省参数 1.缺省参数特性(备胎) 缺省参数是指我们定义函数时有给缺省值的参数&#xf…

chatgpt赋能python:Python开发APP的SEO优化指南

Python开发APP的SEO优化指南 Python是广受欢迎的编程语言&#xff0c;它用于开发各种应用程序&#xff0c;从简单的脚本到大型网络应用程序。SEO优化对于任何应用程序的成功都非常重要&#xff0c;这篇文章将介绍一些Python开发APP的SEO实践和技巧。 为什么需要SEO优化&#…

【OpenMMLab AI实战营第二期】目标检测与MMDetection

目标检测 目标检测的基本范式 划窗 使用卷积实现密集预测 锚框 多尺度检测与FPN 单阶段&无锚框检测器选讲 RPN YOLO、SSD Focal Loss与RetinaNet FCOS YOLO系列选讲 什么是目标检测 目标检测&#xff1a;给定一张图片&#xff0c;用矩形框框出所有感兴趣物体同…

Lucene(8):Lucene底层储存结构

1 详细理解lucene存储结构 存储结构 : 索引(Index) &#xff1a; 一个目录一个索引&#xff0c;在Lucene中一个索引是放在一个文件夹中的。 段(Segment) : 一个索引(逻辑索引)由多个段组成, 多个段可以合并, 以减少读取内容时候的磁盘IO。Lucene中的数据写入会先写内存的一个…

公共场所人流数据统计如何实现?解决重识别、漏检等检测难题

https://github.com/PaddlePaddle/paddledetection当前疫情形势严峻&#xff0c;商场、火车站、地铁口等公共场所对人员流量的统计至关重要。“每天进出多少人&#xff1f;” “现在商场中人员数量有多少&#xff1f;”这些关键数据直接影响到相关防疫管控措施。因为人员基数较…

shardingsphere5.x整合springboot分库分表实战

官方文档不同版本配置变更记录&#xff1a;Spring Boot Start 配置 :: ShardingSphere pom.xml配置&#xff1a; <!--shardingsphere分库分表依赖--> <dependency><groupId>org.apache.shardingsphere</groupId><artifactId>shardingsphere-jd…