Java 8 Streams – Group By Multiple Fields with Collectors.groupingBy()
A guide to group by two or more fields in java 8 streams api. Examples to grouping List by two fields.
1. Overview
In this tutorial, We will learn how to group by multiple fields in java 8 using Streams Collectors.groupingBy() method and example programs with custom objects .
In the previous article, We have shown how to perform Group By in java 8 with Collectors API?
2. Group By Multiple Fields Example in Java 8
First, Create a class Employee with below properties.
int id
String name
String designation
String gender
long salary
Create argument constructor and setter, getters methods for all properties. And also add toString() method to see the Employee object in readable format.
Next, We will try to implement the group by on two fields such as designation and gender . On these two fields get the group by count .
Look at the below examples, you will see the code with the groupingBy() is used twice. This is called as Collectors chaining and observe the output.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
|
package
com.javaprogramto.java8.collectors.groupby;
public
class
Employee {
private
int
id;
private
String name;
private
String designation;
private
String gender;
private
long
salary;
public
Employee(
int
id, String name, String designation, String gender,
long
salary) {
super
();
this
.id = id;
this
.name = name;
this
.designation = designation;
this
.gender = gender;
this
.salary = salary;
}
public
int
getId() {
return
id;
}
public
void
setId(
int
id) {
this
.id = id;
}
public
String getName() {
return
name;
}
public
void
setName(String name) {
this
.name = name;
}
public
String getDesignation() {
return
designation;
}
public
void
setDesignation(String designation) {
this
.designation = designation;
}
public
String getGender() {
return
gender;
}
public
void
setGender(String gender) {
this
.gender = gender;
}
public
long
getSalary() {
return
salary;
}
public
void
setSalary(
long
salary) {
this
.salary = salary;
}
@Override
public
String toString() {
return
"Employee [id="
+ id +
", name="
+ name +
", designation="
+ designation +
", gender="
+ gender
+
", salary="
+ salary +
"]"
;
}
}
|
Example – Group By Two Properties:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
package
com.javaprogramto.java8.collectors.groupby;
import
java.util.ArrayList;
import
java.util.List;
import
java.util.Map;
import
java.util.stream.Collectors;
public
class
GroupingByMultipleFieldsExample {
public
static
void
main(String[] args) {
// Creating List and adding Employees values.
List<Employee> employeesList =
new
ArrayList<>();
employeesList.add(
new
Employee(
101
,
"Glady"
,
"Manager"
,
"Male"
, 25_00_000));
employeesList.add(
new
Employee(
102
,
"Vlad"
,
"Software Engineer"
,
"Female"
, 15_00_000));
employeesList.add(
new
Employee(
103
,
"Shine"
,
"Lead Engineer"
,
"Female"
, 20_00_000));
employeesList.add(
new
Employee(
104
,
"Nike"
,
"Manager"
,
"Female"
, 25_00_000));
employeesList.add(
new
Employee(
105
,
"Slagan"
,
"Software Engineer"
,
"Male"
, 15_00_000));
employeesList.add(
new
Employee(
106
,
"Murekan"
,
"Software Engineer"
,
"Male"
, 15_00_000));
employeesList.add(
new
Employee(
107
,
"Gagy"
,
"Software Engineer"
,
"Male"
, 15_00_000));
// group by - multiple fields
// Grouping by designation and Gender two properties and need to get the count.
Map<String, Map<String, Long>> multipleFieldsMap = employeesList.stream()
.collect(
Collectors.groupingBy(Employee::getDesignation,
Collectors.groupingBy(Employee::getGender,
Collectors.counting())));
// printing the count based on the designation and gender.
System.out.println(
"Group by on multiple properties"
+ multipleFieldsMap);
}
}
|
Output:
1
2
|
Group by on multiple properties
{Software Engineer={Male=
3
, Female=
1
}, Manager={Female=
1
, Male=
1
}, Lead Engineer={Female=
1
}}
|
From the output, you can clearly observe that we can see the count by designation and gender type.
In this program, we have gathered the count of employees but rather than this we can get the list of Employees.
3. Java 8 – Group By Multiple Fields and Collect Aggregated Result into List
First , Collect the list of employees as List<Employee> instead of getting the count . That means inner aggregated Map value type should be List.
To get the list, we should not pass the second argument for the second groupingBy() method.
01
02
03
04
05
06
07
08
09
10
11
|
// Example 2
// group by - multiple fields
// Grouping by designation and Gender two properties and need to get the count.
Map<String, Map<String, List<Employee>>> multipleFieldsMapList = employeesList.stream()
.collect(
Collectors.groupingBy(Employee::getDesignation,
Collectors.groupingBy(Employee::getGender)));
// printing the count based on the designation and gender.
System.out.println(
"Group by on multiple properties and Map key as List"
+ multipleFieldsMapList);
|
Output:
1
2
3
4
5
6
7
8
9
|
Group by on multiple properties and Map key as List
{
Software Engineer={Male=[
Employee [id=
105
, name=Slagan, designation=Software Engineer, gender=Male, salary=
1500000
], Employee [id=
106
, name=Murekan, designation=Software Engineer, gender=Male, salary=
1500000
], Employee [id=
107
, name=Gagy, designation=Software Engineer, gender=Male, salary=
1500000
]],
Female=[Employee [id=
102
, name=Vlad, designation=Software Engineer, gender=Female, salary=
1500000
]]},
Manager={
Female=[Employee [id=
104
, name=Nike, designation=Manager, gender=Female, salary=
2500000
]],
Male=[Employee [id=
101
, name=Glady, designation=Manager, gender=Male, salary=
2500000
]]},
Lead Engineer={Female=[Employee [id=
103
, name=Shine, designation=Lead Engineer, gender=Female, salary=
2000000
]]}}
|
4. Java 8 – Group By Multiple Fields – Avoid Collectors Chaining
We can avoid the Collectors chaining such as calling groupingby() function several times . If we want to group by 4 fields then need to call Collectors.groupingBy() also 4 times which makes code ugly and not readable.
Let us create the separate class with the group by properties and write implementation for equals(), hashcode() methods for object comparisons .
Creating new class for GroupBy fields makes us to call only once the groupingBy() method.
Below examples are implemented as described and suggested way.
GroupBy Class:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
|
class
GroupBy {
private
String designation;
private
String gender;
public
GroupBy(String designation, String gender) {
super
();
this
.designation = designation;
this
.gender = gender;
}
@Override
public
int
hashCode() {
return
this
.designation.length() +
this
.gender.length();
}
@Override
public
boolean
equals(Object obj) {
GroupBy other = (GroupBy) obj;
if
(other.getDesignation().equals(
this
.designation) && other.getGender().equals(
this
.gender))
return
true
;
return
false
;
}
public
String getDesignation() {
return
designation;
}
public
void
setDesignation(String designation) {
this
.designation = designation;
}
public
String getGender() {
return
gender;
}
public
void
setGender(String gender) {
this
.gender = gender;
}
@Override
public
String toString() {
return
"GroupBy [designation="
+ designation +
", gender="
+ gender +
"]"
;
}
}
|
Employee Class:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
package
com.javaprogramto.java8.collectors.groupby.multiple;
public
class
Employee {
private
int
id;
private
String name;
private
long
salary;
private
GroupBy groupBy;
public
Employee(
int
id, String name,
long
salary, GroupBy groupBy) {
super
();
this
.id = id;
this
.name = name;
this
.salary = salary;
this
.groupBy = groupBy;
}
// setters and getters
@Override
public
String toString() {
return
"Employee [id="
+ id +
", name="
+ name +
", salary="
+ salary +
", groupBy="
+ groupBy +
"]"
;
}
}
|
Optimized Group By Multiple Fields Example
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
package
com.javaprogramto.java8.collectors.groupby.multiple;
import
java.util.ArrayList;
import
java.util.List;
import
java.util.Map;
import
java.util.stream.Collectors;
public
class
GroupingByMultipleFieldsExample {
public
static
void
main(String[] args) {
// Creating List and adding Employees values.
List<Employee> employeesList =
new
ArrayList<>();
employeesList.add(
new
Employee(
101
,
"Glady"
, 25_00_000,
new
GroupBy(
"Manager"
,
"Male"
)));
employeesList.add(
new
Employee(
102
,
"Vlad"
, 15_00_000,
new
GroupBy(
"Software Engineer"
,
"Female"
)));
employeesList.add(
new
Employee(
103
,
"Shine"
, 20_00_000,
new
GroupBy(
"Lead Engineer"
,
"Female"
)));
employeesList.add(
new
Employee(
104
,
"Nike"
, 25_00_000,
new
GroupBy(
"Manager"
,
"Female"
)));
employeesList.add(
new
Employee(
105
,
"Slagan"
, 15_00_000,
new
GroupBy(
"Software Engineer"
,
"Male"
)));
employeesList.add(
new
Employee(
106
,
"Murekan"
, 15_00_000,
new
GroupBy(
"Software Engineer"
,
"Male"
)));
employeesList.add(
new
Employee(
107
,
"Gagy"
, 15_00_000,
new
GroupBy(
"Software Engineer"
,
"Male"
)));
// Example 1
// group by - multiple fields
// Grouping by designation and Gender two properties and need to get the count.
Map<GroupBy, Long> multipleFieldsMap = employeesList.stream()
.collect(Collectors.groupingBy(Employee::getGroupBy, Collectors.counting()));
// printing the count based on the designation and gender.
System.out.println(
"Group by on multiple properties"
+ multipleFieldsMap);
}
}
|
Output:
1
2
3
4
5
6
7
|
Group by on multiple properties
{ GroupBy [designation=Lead Engineer, gender=Female]=
1
,
GroupBy [designation=Software Engineer, gender=Male]=
3
,
GroupBy [designation=Software Engineer, gender=Female]=
1
,
GroupBy [designation=Manager, gender=Male]=
1
,
GroupBy [designation=Manager, gender=Female]=
1
}
|
We can record feature from Java 14 alternative to the separate class for group by properties.
5. Grouping By Using Apache Commons Pair.of()
If you have only two fields and do not wish to use the Record or Another class with the Group by properties then we can use the Pair.of() from apache commons library .
1
2
3
4
5
6
7
8
9
|
// Example 3
// group by - multiple fields
// Grouping by designation and Gender two properties with Pair.of()
Map<Pair<String, String>, Long> multipleFieldsMapPair = employeesList.stream()
.collect(Collectors.groupingBy(e -> Pair.of(e.getDesignation(), e.getGender()), Collectors.counting()));
// printing the count based on the designation and gender.
System.out.println(
"Group by on multiple fields with Pair - "
+ multipleFieldsMapPair);
|
Output:
1
2
3
4
5
6
7
8
|
Group by on multiple fields with Pair -
{
(Software Engineer,Male)=
3
,
(Software Engineer,Female)=
1
,
(Lead Engineer,Female)=
1
,
(Manager,Female)=
1
,
(Manager,Male)=
1
}
|
6. Conclusion
In this article, We have seen how to work with the multiple fields in group by using Collectors.groupingBy() method.
Example program to show the best and maintainable code if we have more then 3 fields in the group by combination with custom group by class .
Use apache Pair class if you have only two group by properties .
Published on Java Code Geeks with permission by Venkatesh Nukala, partner at our JCG program . See the original article here: Java 8 Streams – Group By Multiple Fields with Collectors.groupingBy() Opinions expressed by Java Code Geeks contributors are their own. |