Java Stream实现多字段分组groupingBy操作详解

 更新时间:2023年06月08日 08:47:48   作者:草率小猿  
Stream是Java8的一个新特性,主要用户集合数据的处理,如排序、过滤、去重等等功能,本文就来讲讲如何利用Stream实现比较优雅的按多字段进行分组groupingBy吧

近期的项目里,遇到一个需求:对于含有多个元素的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进行处理。

到此这篇关于Java Stream实现多字段分组groupingBy操作详解的文章就介绍到这了,更多相关Java Stream分组内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • websocket在springboot+vue中的使用教程

    websocket在springboot+vue中的使用教程

    这篇文章主要介绍了websocket在springboot+vue中的使用教程,本文通过实例代码给大家介绍的非常详细,具有一定的参考借鉴价值,需要的朋友可以参考下
    2019-08-08
  • Java 反射机制实例详解

    Java 反射机制实例详解

    这篇文章主要介绍了Java 反射机制实例详解的相关资料,这里对java中反射机制进行了详细的分析,需要的朋友可以参考下
    2017-09-09
  • java随机数生成具体实现代码

    java随机数生成具体实现代码

    这篇文章主要为大家分享了java随机数生成具体实现代码,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2016-04-04
  • java扩展Hibernate注解支持java8新时间类型

    java扩展Hibernate注解支持java8新时间类型

    这篇文章主要介绍了java扩展Hibernate注解支持java8新时间类型,需要的朋友可以参考下
    2014-04-04
  • spring boot整合RabbitMQ(Direct模式)

    spring boot整合RabbitMQ(Direct模式)

    springboot集成RabbitMQ非常简单,如果只是简单的使用配置非常少,springboot提供了spring-boot-starter-amqp项目对消息各种支持。下面通过本文给大家介绍下spring boot整合RabbitMQ(Direct模式),需要的朋友可以参考下
    2017-04-04
  • 将JavaWeb项目部署到云服务器的详细步骤

    将JavaWeb项目部署到云服务器的详细步骤

    这篇文章主要介绍了将JavaWeb项目部署到云服务器的详细步骤,文章通过图文结合的方式给大家讲解的非常详细,对大家的学习或工作有一定的帮助,需要的朋友可以参考下
    2024-12-12
  • Druid连接池的自定义过滤功能实现方法

    Druid连接池的自定义过滤功能实现方法

    在数据密集型应用中,监控和分析数据库操作对于确保性能和稳定性至关重要,本文将探讨如何实现一个自定义的Druid过滤器来捕获数据库请求并进行日志记录,以辅助开发和维护工作,需要的朋友可以参考下
    2023-11-11
  • JDK8中新增的Optional工具类基本使用

    JDK8中新增的Optional工具类基本使用

    Optional不是对null关键字的一种替代,而是对于null判定提供了一种更加优雅的实现,接下来通过本文给大家分享JDK8中新增的Optional工具类基本使用,感兴趣的朋友跟随小编一起看看吧
    2021-06-06
  • Java RPC框架熔断降级机制原理解析

    Java RPC框架熔断降级机制原理解析

    这篇文章主要介绍了Java RPC框架熔断降级机制原理解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-02-02
  • Android studio按钮点击页面跳转详细步骤

    Android studio按钮点击页面跳转详细步骤

    在Android应用程序中,页面跳转是非常常见的操作,下面这篇文章主要给大家介绍了关于Android studio按钮点击页面跳转的相关资料,文中通过实例代码介绍的非常详细,需要的朋友可以参考下
    2023-06-06

最新评论