Java stream多字段分组(groupingBy)
近期的项目里,遇到一个需求:对于含有多个元素的
List
,按照其中的某几个属性进行分组,比如Persion::getAge
、Persion::getType
、Persion::getGender
等字段。下面就让我们讨论一下如何比较优雅的按多字段进行分组groupingBy。
利用Stream进行分组
Stream
是Java8的一个新特性,主要用户集合数据的处理,如排序、过滤、去重等等功能,这里我们不展开讲解。本文主要讲解的是利用Stream.collect()
来对List进行分组。
Person类Person.java:
public class Person { 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
即分组字段(本例中为gender字段)的类型,第二个泛型为List
及分组结果的类型。
我们在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
,是一个嵌套了三层的Map,对应的泛型String/Integer/String
分别为对应分组字段的类型,最后一层Map的value类型为List
为实际分组后的数据集合类型,为方便查看数据,特意按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.groupingBy
API会发现,其中一种用法是第一个参数为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,里面包含了分组字段的值,如上图中的male
、35
、teacher
是集合中属性gender/age/type
分别是male
、35
、teacher
的元素集合。数据按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.groupingBy
和Function
进行处理。
来源地址:https://blog.csdn.net/m0_58016522/article/details/131082125
免责声明:
① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。
② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341