Home > Software design >  Using Java Streams Map<String,List<Object>> filter specific value and convert Object to
Using Java Streams Map<String,List<Object>> filter specific value and convert Object to

Time:11-19

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();
  • Related