Java?Stream實現多字段分組groupingBy操作詳解
近期的項目里,遇到一個需求:對于含有多個元素的List<Person>,按照其中的某幾個屬性進行分組,比如Persion::getAge、Persion::getType、Persion::getGender等字段。下面就讓我們討論一下如何比較優(yōu)雅的按多字段進行分組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也會更加多,代碼書寫起來也不太優(yōu)雅。
下面將介紹另外一種按多字段分組的方法。
3. 利用Collectors.groupingBy與Function結合進行多字段分組
查看Collectors.groupingByAPI會發(fā)現,其中一種用法是第一個參數為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只有一層,用該方式分組的結果,對于我們業(yè)務也是比較友好,代碼里對數據處理起來也是比較方便的??梢钥吹剑瑥拇a書寫角度以及分組處理后得到的結果,該方法都是最優(yōu)雅的。
寫在最后
可以看到,如果分組字段只有一個,我們可以用比較簡單的利用Stream.collect()和Collectors.groupingBy進行處理,但對于多個字段的分組操作,建議還是用Collectors.groupingBy和Function進行處理。
到此這篇關于Java Stream實現多字段分組groupingBy操作詳解的文章就介紹到這了,更多相關Java Stream分組內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
websocket在springboot+vue中的使用教程
這篇文章主要介紹了websocket在springboot+vue中的使用教程,本文通過實例代碼給大家介紹的非常詳細,具有一定的參考借鑒價值,需要的朋友可以參考下2019-08-08
spring boot整合RabbitMQ(Direct模式)
springboot集成RabbitMQ非常簡單,如果只是簡單的使用配置非常少,springboot提供了spring-boot-starter-amqp項目對消息各種支持。下面通過本文給大家介紹下spring boot整合RabbitMQ(Direct模式),需要的朋友可以參考下2017-04-04

