I want to calculate the total sum of Salary by personalId.
My hashmap is based on Map<String, List<Employee>>
. key is based on id while value is based on its list.
Here is the list of map shown below
Id, Name , Surname, Salary
personalId1, "Name 1","Surname 1", 100
personalId2, "Name 2","Surname 2", 100
personalId3, "Name 3","Surname 3", 100
personalId1, "Name 1","Surname 1", 100
personalId2, "Name 2","Surname 2", 100
personalId2, "Name 2","Surname 2", 100
.........................
What I really want to get this result shown below.
personalId1, "Name 1","Surname 1", 200
personalId2, "Name 2","Surname 2", 300
personalId3, "Name 3","Surname 3", 100
Here is my dto class shown below
public class SalaryDto {
private String id;
private String name;
private String surname;
private BigDecimal totalSalary;
}
I tried to use java stream to get this result but I have a problem after flatMap. To get the total by personalId, I want to use "reduce".
Here is the code snippet shown below.
List<SalaryDto > totalSalary = employees.values().stream()
.flatMap(List::stream)
.map(e-> new SalaryDto ())
.collect(groupingBy(Personal::getID),Collectors.toList());
How can I do that?
CodePudding user response:
They're already grouped, so all you need to do is reduce each list to a single SalaryDto
:
SalaryDto flatten(List<Employee> employees) {
BigDecimal totalSalary = employees.stream()
.map(Employee::getSalary)
.reduce(BigDecimal::add).get();
return SalaryDto.forEmployee(employees.get(0), totalSalary);
}
And now it's a simple map
call:
List<SalaryDto> salaries = employees.values()
.stream()
.map(this::flatten)
.collect(toMap(SalaryDto::getId, s -> s));
CodePudding user response:
You can define a custom accumulation type for calculating the total salary for each id (it's a common practice to keep DTO immutable, but if you want to avoid introducing new types you can add a new behaviour into SalaryDto
but I would not advice to do so).
public class SalaryAccumulator implements Consumer<Employee> {
private String id;
private String name;
private String surname;
private BigDecimal totalSalary = BigDecimal.ZERO;
public void accept(Employee e) {
if (id == null) id = e.getId();
if (name == null) name = e.getName();
if (surname == null) surname = e.getSurname();
totalSalary = totalSalary.add(e.getSalary());
}
public SalaryAccumulator merge(SalaryAccumulator other) {
totalSalary = totalSalary.add(other.getTotalSalary());
return this;
}
public SalaryDto toSalaryDto() {
return new SalaryDto(id, name, surname, totalSalary);
}
}
And in the stream in order to turn each List
of Employee
into a SalaryDto
you can generate a stream over the list and apply collect()
passing a custom Collector as an argument.
To define a custom Collector, we need to make of the static factory method Collector.of()
. That's where SalaryAccumulator
would come into play, it would serve as a mutable container of the collector.
Map<String, List<Employee>> employees = Map.of();
List<SalaryDto> totalSalary = employees.values().stream()
.map(list -> list.stream()
.collect(Collector.of(
SalaryAccumulator::new,
SalaryAccumulator::accept,
SalaryAccumulator::merge,
SalaryAccumulator::toSalaryDto
))
).toList();