Search code examples
javajava-streamcollectors

Java Stream - maxBy - How to find a department with the highest Average salary with Java 8


I have a list of Employee.

public class Employee {
    private String department;
    private double salary;
    // other properties
}

I am trying to find the name of the department which has the highest average salary.

The code I've tried:

List<Employee> employeeList = new ArrayList<Employee>();
employeeList.add(new Employee("abc", 38, "M", "QA", 115000.00d, 2016));
employeeList.add(new Employee("def", 23, "M", "Sports", 5000000.00d, 2011));
employeeList.add(new Employee("ghi", 38, "M", "QA", 120000.00d, 2007));
employeeList.add(new Employee("jkl", 35, "M", "DEV", 50000.00d, 2016));
employeeList.add(new Employee("mno", 30, "F", "QA", 80000.00d, 206));
employeeList.add(new Employee("pqr", 32, "F", "DEV", 75000.00d, 2014));

Map<String, Double> highestAvgSalary = employeeList.stream()
    .collect(Collectors.groupingBy(
        emp -> emp.getDepartment(),
        Collectors.averagingDouble(emp -> emp.getSalary(),                        
            Collectors.maxBy(Comparator.comparingDouble(emp -> emp.getSalary()))

The last line of the code is triggering a compilation error. How do I have to apply maxBy() to get maximum avg salary of the department?


Solution

  • Collectors.averagingDouble() expects only one argument - a function which turn stream element into Double. Attempt of passing a downstream collector is incorrect.

    This task can't be fulfilled in a single iteration over the collection of employees as mentioned by @Sayan Bhattacharya in the comments.

    We need to create a stream over the map entries of the intermediate Map that associates debarment-name with its average salary and apply max() operation on it.

    Map.Entry<String, Double> highestAvgSalary = employeeList.stream()
        .collect(Collectors.groupingBy(
            Employee::getDepartment,
            Collectors.averagingDouble(Employee::getSalary)
        ))
        .entrySet().stream()
        .max(Map.Entry.comparingByValue())
        .get();
    

    Note that max() produces an Optional. And Optional API offers different ways to extract result from it. Method get() might produce a NoSuchElementException, in case of an empty optional. If you have a custom exception for that case - replace it with orElseThrow(Supplier). Since Java 10 according to the documentation get() isn't encouraged to be used (unless you're not sure that result is present), the recommended practice is to use the parameterless flavor of orElseThrow(). Obviously, you can't use it with Java 8, only for that reason in the code above I've applied get() (with lower versions it's somewhat justifiable to avoid verbosity). But you still have other alternatives like orElse() and orElseGet().

    Also note that having Map as the resulting type would not be very handy since you don't know the name of the department with the highest salary in advance (which would be the key of the only entry contained in the map). Hence, you can't access the information with in such map in a convenient way. But if you really need a single-entry-map for some reason, then instead of get() in the code above, you can apply map(Map::ofEntries).get() and it would give you Map<String, Double> as a result.