简体   繁体   中英

get average salary with Java stream api;

how can i get the average salary of company employees whose salary is more than salaryLimit? I think first I need to get a list of all salaries and filter it + use mapToInt and average, but...nothing works((

public class TestCl {

@Data
@AllArgsConstructor
public class Company {
    private List<Department> departments;
}

@Data
@AllArgsConstructor
public class Department {
    private List<Employee> employees;
}

@Data
@AllArgsConstructor
public class Employee {
    private int salary;
}

@Test
public void test() {
    int salaryLimit = 25;

    List<Employee> employees = new ArrayList<>(){{
        add(new Employee(10));
        add(new Employee(20));
        add(new Employee(30));
        add(new Employee(40));
        add(new Employee(50));
    }};

    List<Department> departments = new ArrayList<>(){{
        add(new Department(employees));
        add(new Department(employees));
        add(new Department(employees));
        add(new Department(employees));
        add(new Department(employees));
    }};

    Company company = new Company(departments);

    float average = company.getDepartments().stream()
            .map(Department::getEmployees).toList().stream()
            .map(Employee::getSalary).toList().stream()
            .filter(x -> x >= salaryLimit).mapToInt().average();

    System.out.println(average);
}

}

For one thing, you're looking for Stream.flatMap() . For example,

company.getDepartments().stream()
            .flatMap(d -> d.getEmployees().stream()) // ...

That converts your stream of Departments to a stream of the Employees of each department. You can then map() those to salaries and filter as you already do.

The combination .toList().stream() makes zero sense, however, as it expends work to convert one stream to another one providing exactly the same objects. Removing that has only upside.

To map to an IntStream or, probably better for this purpose, a DoubleStream , the mapToInt() or mapToDouble() method requires a function expressing the details of the mapping, which you are omitting.

Finally, IntStream.average() , DoubleStream.average() , etc return an OptionalInteger / OptionalDouble / etc , which accommodates the possibility that there is no average on account of the stream providing zero elements. You need to deal with that.

All together, then:

float average = company.getDepartments().stream() // a stream of Departments
        .flatMap(d -> d.getEmployees().stream())  // a stream of Employees
        .map(e -> e.getSalary())                  // a stream of Integer salaries
        .filter(s -> s.intValue() >= salaryLimit) // only the high salaries
        .mapToDouble(Integer::doubleValue)        // as a DoubleStream
        .average()                                // reduce to an average
        .getAsDouble();                           // the value, if it exists

Note well that the getAsDouble() will throw NoSuchElementException if no salaries satisfy the filter criterion. Catching and handling that is a perfectly reasonable way to deal with the situation, but if you want to avoid that then you can store the result of average() instead of chaining getAsDouble() , and then test it with isPresent() to decide whether to go ahead with getAsDouble() or to handle the situation differently. Or if there is a specific double value that you want to provide in that case -- such as 0 or Double.NaN -- then you could chain on an orElse(valueForZeroSalaries) instead of the getAsDouble() .

What you're getting is a compiler error, because mapToInt requires a ToIntFunction but you're providing none.

Also you don't need to collect the stream to a list only to convert it to a stream again. You can still use your stream as long as you don't call a terminal method.

So what you can do is something like this:

double average = company.getDepartments().stream()
        .flatMap(department -> department.getEmployees().stream())
        .map(Employee::getSalary)
        .filter(x -> x >= salaryLimit)
        .mapToDouble(salary -> salary)
        .average()
        .orElse(0.0);

average() gives you an Optional<Double> so you need to unwrap the value at the end. As a default value you can return 0.0 with a call to orElse

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM