To group by multiple values you could:
Create a class representing the grouped values, map each employee to a grouped instance and then group by it.
Use nested downstreams within the groupingby
operation. However, as a result you would get nested maps to deal with.
Use a List
initialized with the values you want to group by (easy and quick workaround).
Here's also a link with more in depth solutions on how to group by multiple values:
Group by multiple field names in java 8
Solution with List of Default Employees (what you requested)
//Grouping by multiple fields with a List workaround instead of using nested groupingBy downstreams that would return nested Maps
Map<List, List<Employee>> mapGroupedBy = listEmployees.stream()
.collect(Collectors.groupingBy(e -> Arrays.asList(e.getDepartment(), e.getDesignation(), e.getGender())));
//Returning an ArrayList with the average values
List<Employee> listEmployeesResult = mapGroupedBy.values().stream()
.collect(ArrayList::new,
(listCollect, listGrouped) -> listCollect.add(new Employee(null, null, null, null,
(int) Math.round(listGrouped.stream().collect(Collectors.averagingDouble(Employee::getSalary)).doubleValue()),
(int) Math.round(listGrouped.stream().collect(Collectors.averagingDouble(Employee::getBonus)).doubleValue()),
(int) Math.round(listGrouped.stream().collect(Collectors.averagingDouble(Employee::getPerks)).doubleValue()))),
(list1, list2) -> list1.addAll(list2));
The problem I see with what you requested is that you won't be able to tell from which group of values each averaged employee comes from.
Solution with Map of Default Employees (my recommendation)
//Grouping by multiple fields with a List workaround instead of using nested groupingBy downstreams that would return nested Maps
Map<List, List<Employee>> mapGroupedBy = listEmployees.stream()
.collect(Collectors.groupingBy(e -> Arrays.asList(e.getDepartment(), e.getDesignation(), e.getGender())));
//Map of known keys for each n-upla (list) of values
Map<List, Employee> mapResult = new HashMap<>();
//For each entry of the grouped map we generate a new entry for the result map by "mapping" each grouped list into a default Employee with no information and average values
mapGroupedBy.entrySet().stream()
.forEach(entry -> mapResult.put(entry.getKey(), new Employee(null, null, null, null,
(int) Math.round(entry.getValue().stream().collect(Collectors.averagingDouble(Employee::getSalary)).doubleValue()),
(int) Math.round(entry.getValue().stream().collect(Collectors.averagingDouble(Employee::getBonus)).doubleValue()),
(int) Math.round(entry.getValue().stream().collect(Collectors.averagingDouble(Employee::getPerks)).doubleValue()))));
In this scenario, you're more likely to discern your output and work with it.
Test Main
public class Test {
public static void main(String[] args) {
List<Employee> listEmployees = new ArrayList<>(List.of(
new Employee("Mark Hoppus", "Marketing", "male", "Sales Manager", 2200, 200, 1),
new Employee("Tom DeLonge", "Marketing", "male", "Sales Manager", 2800, 0, 1),
new Employee("Travis Barker", "Marketing", "male", "Sales Manager", 3850, 800, 6),
new Employee("Aretha Franklin", "Marketing", "female", "Sales Manager", 2900, 300, 3),
new Employee("Diana Ross", "Marketing", "female", "Sales Manager", 1900, 0, 1),
new Employee("Keith Flint", "R&D", "male", "Software Engineer", 4000, 600, 0),
new Employee("Liam Howlett", "R&D", "male", "Software Engineer", 5200, 250, 2),
new Employee("Whitney Houston", "R&D", "female", "Software Engineer", 6000, 1000, 8),
new Employee("Tina Turner", "R&D", "female", "Software Engineer", 7500, 450, 9)
));
//Grouping by multiple fields with a List workaround instead of using nested groupingBy downstreams that would return nested Maps
Map<List, List<Employee>> mapGroupedBy = listEmployees.stream()
.collect(Collectors.groupingBy(e -> Arrays.asList(e.getDepartment(), e.getDesignation(), e.getGender())));
//Returning an ArrayList with the average values
List<Employee> listEmployeesResult = mapGroupedBy.values().stream()
.collect(ArrayList::new,
(listCollect, listGrouped) -> listCollect.add(new Employee(null, null, null, null,
(int) Math.round(listGrouped.stream().collect(Collectors.averagingDouble(Employee::getSalary)).doubleValue()),
(int) Math.round(listGrouped.stream().collect(Collectors.averagingDouble(Employee::getBonus)).doubleValue()),
(int) Math.round(listGrouped.stream().collect(Collectors.averagingDouble(Employee::getPerks)).doubleValue()))),
(list1, list2) -> list1.addAll(list2));
//Printing the ArrayList with no indication of where those average values come from
System.out.println("Printing list results");
for (Employee e : listEmployeesResult) {
System.out.printf("Salary: %d - Bonus: %d - Perks: %d%n", e.getSalary(), e.getBonus(), e.getPerks());
}
//Map of known keys for each n-upla (list) of values
Map<List, Employee> mapResult = new HashMap<>();
//For each entry of the grouped map we generate a new entry for the result map by "mapping" each grouped list into a default Employee with no information and average values
mapGroupedBy.entrySet().stream()
.forEach(entry -> mapResult.put(entry.getKey(), new Employee(null, null, null, null,
(int) Math.round(entry.getValue().stream().collect(Collectors.averagingDouble(Employee::getSalary)).doubleValue()),
(int) Math.round(entry.getValue().stream().collect(Collectors.averagingDouble(Employee::getBonus)).doubleValue()),
(int) Math.round(entry.getValue().stream().collect(Collectors.averagingDouble(Employee::getPerks)).doubleValue()))));
System.out.println("\nPrinting map results");
for (List keyList : mapResult.keySet()) {
System.out.printf("%s => Salary: %d - Bonus: %d - Perks: %d%n", keyList, mapResult.get(keyList).getSalary(), mapResult.get(keyList).getBonus(), mapResult.get(keyList).getPerks());
}
}
}
Here, I've implemented both solutions and showed their differences.
Output
For some reasons, the output is not displayed and I had to paste the link.
https://i.stack.imgur.com/fHDun.png