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);
}
}
CodePudding user response:
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()
.
CodePudding user response:
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
CodePudding user response:
if(salary > salaryLimit){
getAverageSalary();
}
You can try out this once.