I have POJO definition as follows:
class EmployeeDetails{
private String deptName;
private Double salary;
private Double bonus;
...
}
Currently, i have lambda expression for Group By 'deptName'
as :
$set.stream().collect(Collectors.groupingBy(EmployeeDetails::getDeptName,
Collectors.summingLong(EmployeeDetails::getSalary));
Question Is it possible to Sum more than one column? I need to compute sum on both fields salary and bonus
in one expression instead of multiple times?
SQL representation would be:
SELECT deptName,SUM(salary),SUM(bonus)
FROM TABLE_EMP
GROUP BY deptName;
You need to create an additional class that will hold your 2 summarised numbers (salary and bonus). And a custom collector.
Let's say you have
private static final class Summary {
private double salarySum;
private double bonusSum;
public Summary() {
this.salarySum = 0;
this.bonusSum = 0;
}
@Override
public String toString() {
return "Summary{" +
"salarySum=" + salarySum +
", bonusSum=" + bonusSum +
'}';
}
}
for holding sums. Then you need a collector like this:
private static class EmployeeDetailsSummaryCollector implements Collector<EmployeeDetails, Summary, Summary> {
@Override
public Supplier<Summary> supplier() {
return Summary::new;
}
@Override
public BiConsumer<Summary, EmployeeDetails> accumulator() {
return (summary, employeeDetails) -> {
summary.salarySum += employeeDetails.salary;
summary.bonusSum += employeeDetails.bonus;
};
}
@Override
public BinaryOperator<Summary> combiner() {
return (summary, summary1) -> {
summary.salarySum += summary1.salarySum;
summary.bonusSum += summary1.bonusSum;
return summary;
};
}
@Override
public Function<Summary, Summary> finisher() {
return Function.identity();
}
@Override
public Set<Characteristics> characteristics() {
return EnumSet.of(Collector.Characteristics.IDENTITY_FINISH);
}
}
With these classes you can collect your results like
final List<EmployeeDetails> employees = asList(
new EmployeeDetails(/* deptName */"A", /* salary */ 100d, /* bonus */ 20d),
new EmployeeDetails("A", 150d, 10d),
new EmployeeDetails("B", 80d, 5d),
new EmployeeDetails("C", 100d, 20d)
);
final Collector<EmployeeDetails, Summary, Summary> collector = new EmployeeDetailsSummaryCollector();
final Map<String, Summary> map = employees.stream()
.collect(Collectors.groupingBy(o -> o.deptName, collector));
System.out.println("map = " + map);
Which prints this:
map = {A=[salary=250.0, bonus=30.0], B=[salary=80.0, bonus=5.0], C=[salary=100.0, bonus=20.0]}
I know you've got your answer, but here is my take(I was writing while the other was posted). There is already a Pair
in java in the form of AbstractMap.SimpleEntry
.
System.out.println(Stream.of(new EmployeeDetails("first", 50d, 7d), new EmployeeDetails("first", 50d, 7d),
new EmployeeDetails("second", 51d, 8d), new EmployeeDetails("second", 51d, 8d))
.collect(Collectors.toMap(EmployeeDetails::getDeptName,
ed -> new AbstractMap.SimpleEntry<>(ed.getSalary(), ed.getBonus()),
(left, right) -> {
double key = left.getKey() + right.getKey();
double value = left.getValue() + right.getValue();
return new AbstractMap.SimpleEntry<>(key, value);
}, HashMap::new)));
Grouping by is a terminal operation that yields a map. The map produced by the groupingBy in the code below is a Map<String, List<EmployeeDetails>>
. I create a new stream using the Map entrySet method. I then create a new Map using Collectors.toMap
. This approach uses method chaining to avoid creating another class and create more concise code.
details.stream()
.collect(Collectors.groupingBy(EmployeeDetails::getDeptName))
.entrySet()
.stream()
.collect(Collectors.toMap(x->x.getKey(), x->x.getValue()
.stream()
.mapToDouble(y -> y.getSalary() + y.getBonus())
.sum()));
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With