Definition
- groupingBy(dept, summarizingDouble(salary)) produces
Map. - DoubleSummaryStatistics provides
getCount(),getSum(),getMin(),getMax(),getAverage(). - One pass per department collects all five metrics simultaneously.
- Formatted report: use
printforString.formatfor aligned output.
The bank CFO asks for a one-page salary report: for each department, show how many employees, total payroll, lowest salary, highest salary, and average salary. One DoubleSummaryStatistics per department provides all five numbers.
Understanding the Concepts
How do you create a per-department summary report?
package com.bank.hr;
import java.util.*;
import java.util.stream.Collectors;
public class DepartmentSummaryReport {
public static void main(String[] args) {
List<Employee> employees = Employee.getSampleEmployees();
Map<String, DoubleSummaryStatistics> deptStats = employees.stream()
.collect(Collectors.groupingBy(
Employee::getDepartment,
Collectors.summarizingDouble(
e -> e.getSalary().doubleValue())));
System.out.printf("%-15s %5s %15s %12s %12s %12s%n",
"Department", "Count", "Total", "Min", "Max", "Average");
System.out.println("-".repeat(75));
new TreeMap<>(deptStats).forEach((dept, s) ->
System.out.printf("%-15s %5d %,15.0f %,12.0f %,12.0f %,12.0f%n",
dept, s.getCount(), s.getSum(),
s.getMin(), s.getMax(), s.getAverage()));
}
}
Department Count Total Min Max Average
---------------------------------------------------------------------------
COMPLIANCE 2 2,100,000 800,000 1,300,000 1,050,000
OPERATIONS 2 1,800,000 700,000 1,100,000 900,000
RISK 3 3,600,000 900,000 1,500,000 1,200,000
TRADING 3 4,500,000 1,200,000 1,800,000 1,500,000
What are common mistakes?
Mistake: computing each metric separately with five different streams.
// WRONG: five separate passes through the data
Map<String, Long> count = ...groupingBy(dept, counting());
Map<String, Double> sum = ...groupingBy(dept, summingDouble(salary));
Map<String, Double> avg = ...groupingBy(dept, averagingDouble(salary));
// etc. - wasteful!
// CORRECT: one pass with summarizingDouble
Map<String, DoubleSummaryStatistics> all = ...groupingBy(dept, summarizingDouble(salary));
What about adding a grand total row?
import java.util.DoubleSummaryStatistics;
DoubleSummaryStatistics grandTotal = employees.stream()
.collect(Collectors.summarizingDouble(
e -> e.getSalary().doubleValue()));
System.out.printf("%-15s %5d %,15.0f %,12.0f %,12.0f %,12.0f%n",
"TOTAL", grandTotal.getCount(), grandTotal.getSum(),
grandTotal.getMin(), grandTotal.getMax(), grandTotal.getAverage());
What about using exact BigDecimal for totals?
Map<String, BigDecimal> exactTotals = employees.stream()
.collect(Collectors.groupingBy(
Employee::getDepartment,
Collectors.reducing(
BigDecimal.ZERO,
Employee::getSalary,
BigDecimal::add)));
DoubleSummaryStatistics has floating-point drift. For audited totals, use BigDecimal reduce per department.
What about exporting as CSV?
String csv = new TreeMap<>(deptStats).entrySet().stream()
.map(e -> String.format("%s,%d,%.0f,%.0f,%.0f,%.0f",
e.getKey(), e.getValue().getCount(),
e.getValue().getSum(), e.getValue().getMin(),
e.getValue().getMax(), e.getValue().getAverage()))
.collect(Collectors.joining("\n",
"Department,Count,Total,Min,Max,Average\n", ""));
What about sorting by a specific metric?
// Sort departments by total salary descending
deptStats.entrySet().stream()
.sorted(Comparator.comparingDouble(
(Map.Entry<String, DoubleSummaryStatistics> e) ->
e.getValue().getSum()).reversed())
.forEach(e -> System.out.println(
e.getKey() + ": Rs." + e.getValue().getSum()));
What are the edge cases?
An empty employee list produces an empty map. A department with one employee has min == max == average == that salary, and count == 1.
What are common interview follow-up questions?
- "How do you add a grand total row?" Compute a separate DoubleSummaryStatistics for all employees.
- "How do you export as CSV?" Map the formatted strings to CSV format with joining("\n").
Visual Overview
groupingBy(dept, summarizingDouble(salary)) ┌──────────────────────────────────────────────────┐ │ Dept Count Total Min Max Avg │ │ TRADING 3 4.5M 1.2M 1.8M 1.5M │ │ RISK 3 3.6M 0.9M 1.5M 1.2M │ │ OPERATIONS 2 1.8M 0.7M 1.1M 0.9M │ │ COMPLIANCE 2 2.1M 0.8M 1.3M 1.05M│ └──────────────────────────────────────────────────┘
Key Takeaway
A department summary report uses groupingBy with summarizingDouble to collect all five salary metrics (count, sum, min, max, average) per department in a single pass. One DoubleSummaryStatistics object per department provides all the numbers needed for a dashboard card.
Remember one sentence: summarizingDouble as a downstream gives count, sum, min, max, and average per group in one pass, replacing five separate aggregation queries.
---