Definition
- summarizingDouble(ToDoubleFunction) – collects stream elements into
DoubleSummaryStatistics, tracking count, sum, min, max, and average simultaneously in a single pass. - getCount() – returns
long, whilegetSum(),getMin(),getMax(), andgetAverage()returndouble. - Empty stream – produces count=0, sum=0.0, average=0.0, min=Double.POSITIVE_INFINITY, and max=Double.NEGATIVE_INFINITY.
- Siblings –
summarizingIntproducesIntSummaryStatisticsandsummarizingLongproducesLongSummaryStatistics. - Per-group –
groupingBy(department, summarizingDouble(salary))gives statistics per department.
Priya at the National Bank of India needs a salary report card: how many employees, total payroll, lowest salary, highest salary, and average salary. Instead of walking the employee list five times (once per metric), she walks it once carrying a single notepad that updates all five numbers for every salary she sees. That notepad is DoubleSummaryStatistics.
Understanding the Concepts
What does this question ask?
The interviewer wants you to produce a complete statistical summary of a numeric field in one stream pass. This tests your knowledge of Collectors.summarizingDouble and the DoubleSummaryStatistics class.
How do you compute salary statistics in a single pass?
package com.bank.hr;
import java.util.*;
import java.util.stream.Collectors;
public class SalarySummary {
public static void main(String[] args) {
List<Employee> employees = Employee.getSampleEmployees();
DoubleSummaryStatistics stats = employees.stream()
.collect(Collectors.summarizingDouble(
e -> e.getSalary().doubleValue()));
System.out.println("Count: " + stats.getCount());
System.out.printf("Sum: Rs.%.2f%n", stats.getSum());
System.out.printf("Min: Rs.%.2f%n", stats.getMin());
System.out.printf("Max: Rs.%.2f%n", stats.getMax());
System.out.printf("Average: Rs.%.2f%n", stats.getAverage());
}
}
Count: 10
Sum: Rs.12200000.00
Min: Rs.700000.00
Max: Rs.1800000.00
Average: Rs.1220000.00
One pass produces five metrics. In the old for-loop approach, you would maintain separate count, sum, min, and max variables.
What happens with an empty stream?
DoubleSummaryStatistics empty = List.<Employee>of().stream()
.collect(Collectors.summarizingDouble(
e -> e.getSalary().doubleValue()));
System.out.println("Count: " + empty.getCount()); // 0
System.out.println("Sum: " + empty.getSum()); // 0.0
System.out.println("Average: " + empty.getAverage()); // 0.0
System.out.println("Min: " + empty.getMin()); // Infinity
System.out.println("Max: " + empty.getMax()); // -Infinity
Count: 0
Sum: 0.0
Average: 0.0
Min: Infinity
Max: -Infinity
The sentinel values Double.POSITIVE_INFINITY for min and Double.NEGATIVE_INFINITY for max are confusing on a dashboard. Always check stats.getCount() > 0 before using min and max.
How do you get per-department statistics?
Combine groupingBy with summarizingDouble as the downstream.
package com.bank.hr;
import java.util.*;
import java.util.stream.Collectors;
public class StatsPerDepartment {
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())));
deptStats.forEach((dept, s) ->
System.out.printf("%s: count=%d, avg=Rs.%.0f, min=Rs.%.0f, max=Rs.%.0f%n",
dept, s.getCount(), s.getAverage(), s.getMin(), s.getMax()));
}
}
TRADING: count=3, avg=Rs.1500000, min=Rs.1200000, max=Rs.1800000
RISK: count=3, avg=Rs.1200000, min=Rs.900000, max=Rs.1500000
OPERATIONS: count=2, avg=Rs.900000, min=Rs.700000, max=Rs.1100000
COMPLIANCE: count=2, avg=Rs.1050000, min=Rs.800000, max=Rs.1300000
How is summarizingDouble different from summarizingInt and summarizingLong?
| Collector | Mapper type | Stats class | Sum type | Use for |
|---|---|---|---|---|
| summarizingInt | ToIntFunction | IntSummaryStatistics | long | age, count, years |
| summarizingLong | ToLongFunction | LongSummaryStatistics | long | timestamps, large counts |
| summarizingDouble | ToDoubleFunction | DoubleSummaryStatistics | double | salary, percentage, rating |
// summarizingInt for age
IntSummaryStatistics ageStats = employees.stream()
.collect(Collectors.summarizingInt(Employee::getAge));
// summarizingDouble for salary (BigDecimal converted to double)
DoubleSummaryStatistics salaryStats = employees.stream()
.collect(Collectors.summarizingDouble(
e -> e.getSalary().doubleValue()));
What are common mistakes?
Mistake 1: not handling the empty stream sentinel values.
// WRONG: displaying min/max without checking count
DoubleSummaryStatistics stats = getEmployees().stream()
.collect(Collectors.summarizingDouble(e -> e.getSalary().doubleValue()));
System.out.println("Min salary: " + stats.getMin()); // shows Infinity for empty!
// CORRECT: guard with count check
if (stats.getCount() > 0) {
System.out.println("Min salary: Rs." + stats.getMin());
} else {
System.out.println("No employees to summarize");
}
Mistake 2: using summarizingDouble for exact money totals.
// WRONG for audited figures: floating-point drift
DoubleSummaryStatistics approx = employees.stream()
.collect(Collectors.summarizingDouble(
e -> e.getSalary().doubleValue()));
// Sum might be 12199999.999999998 instead of 12200000
// CORRECT for exact totals: use BigDecimal reduce
import java.math.BigDecimal;
BigDecimal exactSum = employees.stream()
.map(Employee::getSalary)
.reduce(BigDecimal.ZERO, BigDecimal::add);
What about the mapToDouble + summaryStatistics alternative?
You can also get the same statistics object using mapToDouble followed by summaryStatistics().
// Using Collectors.summarizingDouble
DoubleSummaryStatistics via1 = employees.stream()
.collect(Collectors.summarizingDouble(
e -> e.getSalary().doubleValue()));
// Using mapToDouble + summaryStatistics
DoubleSummaryStatistics via2 = employees.stream()
.mapToDouble(e -> e.getSalary().doubleValue())
.summaryStatistics();
// Both produce the same result
The mapToDouble approach is slightly more efficient because it avoids boxing: the stream becomes a DoubleStream of primitives. However, only the Collectors.summarizingDouble approach works as a downstream inside groupingBy.
What are common interview follow-up questions?
- "How do you combine statistics from two separate streams?" Use
stats1.combine(stats2)which merges the counts and sums. - "Can you create a custom summary that tracks median?" No,
DoubleSummaryStatisticsdoes not track median. You need a sorted list or a custom collector for that. - "How do you format the statistics as a report?" Use
String.formatorprintfwith the getter methods.
Visual Overview
Stream<Employee>
[700K, 900K, 1000K, 1100K, 1200K, 1300K, 1400K, 1500K, 1600K, 1800K]
│
│ .collect(summarizingDouble(salary))
▼
┌──────────────────────────────────────┐
│ DoubleSummaryStatistics │
│ │
│ count = 10 │
│ sum = 12,200,000.00 │
│ min = 700,000.00 │
│ max = 1,800,000.00 │
│ average = 1,220,000.00 │
└──────────────────────────────────────┘
Empty stream sentinel values:
count=0, sum=0.0, avg=0.0
min=+Infinity, max=-Infinity ← watch out!
Key Takeaway
Collectors.summarizingDouble produces a DoubleSummaryStatistics object that tracks count, sum, min, max, and average in a single pass through the stream. One traversal replaces five separate computations and produces a consistent snapshot of all metrics at once.
The critical gotcha is the empty stream case: count and sum are zero, but min is positive infinity and max is negative infinity. These sentinel values look confusing on a dashboard, so always check stats.getCount() > 0 before displaying min and max.
For per-department statistics, combine with groupingBy: groupingBy(department, summarizingDouble(salary)) produces a Map where each department maps to its own DoubleSummaryStatistics. For exact monetary totals in audited reports, use BigDecimal.reduce instead of summarizingDouble, which is subject to floating-point drift.
Remember one sentence: summarizingDouble gives count, sum, min, max, and average in one pass, but check for empty streams because min defaults to positive infinity.
---