Back to Stream: map & Transform
Q22Question 22 of 34

Use mapToDouble(...).summaryStatistics() to get count, sum, min, max, avg in one pass

Key Concepts
  • summaryStatistics() computes count, sum, min, max, and average in a single pass over the stream
  • Returns DoubleSummaryStatistics for DoubleStream, IntSummaryStatistics for IntStream, LongSummaryStatistics for LongStream
  • For an empty stream, count and sum are 0, average is 0.0, min is Double.MAX_VALUE, max is Double.MIN_VALUE
  • Always check getCount() before trusting getMin() and getMax() on potentially empty streams
  • Collectors.summarizingDouble() produces the same object inside a groupingBy for per-group statistics
  • One traversal for five figures is the efficiency gain interviewers want to hear

Definition

summaryStatistics() computes count, sum, min, max, and average together in a single traversal of the stream.
  • summaryStatistics() is a terminal operation on a primitive stream that returns a statistics object.
  • DoubleSummaryStatistics holds count, sum, min, max, and average computed in one pass.
  • IntSummaryStatistics and LongSummaryStatistics serve IntStream and LongStream respectively.
  • Collectors.summarizingDouble() produces the same statistics object inside a collect, useful as a downstream in groupingBy.

Instead of running through the salary ledger five separate times to get the head count, total, smallest, largest, and average, Priya at the National Bank of India makes one pass and fills in all five boxes on a summary slip at once.

Understanding the Concepts

What is summaryStatistics?

summaryStatistics() is a terminal operation on a primitive stream that computes five numeric measures in a single traversal: count, sum, minimum, maximum, and average. It returns a DoubleSummaryStatistics object (or IntSummaryStatistics or LongSummaryStatistics for the corresponding stream types).

This is both faster and clearer than computing each statistic with a separate stream, because each separate stream would traverse the data from scratch.

JAVA
package com.bank.hr;

import java.util.*;
import java.util.DoubleSummaryStatistics;

public class SalarySummary {
    public static void main(String[] args) {
        List<Employee> employees = List.of(
            new Employee("Rajesh", "Loans",   75000.00),
            new Employee("Priya",  "Loans",   82000.00),
            new Employee("Amit",   "IT",      68000.00),
            new Employee("Kavita", "IT",      91000.00),
            new Employee("Neha",   "Finance", 78000.00)
        );

        DoubleSummaryStatistics stats = employees.stream()
            .mapToDouble(Employee::getSalaryAsDouble)
            .summaryStatistics();

        System.out.println("Count:   " + stats.getCount());
        System.out.println("Sum:     " + stats.getSum());
        System.out.println("Min:     " + stats.getMin());
        System.out.println("Max:     " + stats.getMax());
        System.out.println("Average: " + stats.getAverage());
    }
}
TEXT
Count:   5
Sum:     394000.0
Min:     68000.0
Max:     91000.0
Average: 78800.0

How is this different from calling average(), sum(), min() separately?

Each terminal operation consumes the stream, so you cannot call sum() and then average() on the same stream. With separate operations you must create a new stream each time, traversing the data multiple times.

| Approach | Passes | Streams created | Result |
|---|---|---|---|
| summaryStatistics() | 1 | 1 | All five statistics |
| Separate sum(), average(), min(), max(), count() | 5 | 5 | Same five statistics |

JAVA
// WRONG: stream already consumed after sum()
var ds = employees.stream().mapToDouble(Employee::getSalaryAsDouble);
double sum = ds.sum();
double avg = ds.average().orElse(0);  // IllegalStateException!

// CORRECT: one pass with summaryStatistics
var stats = employees.stream()
    .mapToDouble(Employee::getSalaryAsDouble)
    .summaryStatistics();
double sum2 = stats.getSum();
double avg2 = stats.getAverage();

How do you compute per-department summary statistics?

Collectors.summarizingDouble() produces the same DoubleSummaryStatistics object, but works inside a collect. Nest it as a downstream collector in groupingBy to get per-group statistics.

JAVA
import java.util.stream.Collectors;

Map<String, DoubleSummaryStatistics> deptStats = employees.stream()
    .collect(Collectors.groupingBy(
        Employee::getDepartment,
        Collectors.summarizingDouble(Employee::getSalaryAsDouble)));

deptStats.forEach((dept, s) ->
    System.out.printf("%s: count=%d, avg=%.0f, min=%.0f, max=%.0f%n",
        dept, s.getCount(), s.getAverage(), s.getMin(), s.getMax()));
TEXT
Loans: count=2, avg=78500, min=75000, max=82000
IT: count=2, avg=79500, min=68000, max=91000
Finance: count=1, avg=78000, min=78000, max=78000

What happens with an empty stream?

For an empty DoubleStream, summaryStatistics() returns an object with count 0, sum 0.0, average 0.0, but min as Double.MAX_VALUE and max as Double.MIN_VALUE (sentinel values). Always check getCount() before trusting min and max.

JAVA
DoubleSummaryStatistics empty = java.util.stream.DoubleStream.empty()
    .summaryStatistics();

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());     // 1.7976931348623157E308
System.out.println("Max:     " + empty.getMax());     // 4.9E-324

What are common mistakes?

Mistake: trusting min and max on an empty stream without checking count.

JAVA
// WRONG: min is MAX_VALUE sentinel, not a real salary
var stats = List.<Employee>of().stream()
    .mapToDouble(Employee::getSalaryAsDouble)
    .summaryStatistics();
System.out.println("Min salary: " + stats.getMin());
// Prints: Min salary: 1.7976931348623157E308  (meaningless!)

// CORRECT: check count first
if (stats.getCount() > 0) {
    System.out.println("Min salary: " + stats.getMin());
} else {
    System.out.println("No employees to report");
}

Can you combine two DoubleSummaryStatistics objects?

Yes. The combine() method merges two statistics objects, which is how parallel streams work internally.

JAVA
DoubleSummaryStatistics branch1 = new DoubleSummaryStatistics();
branch1.accept(75000.0);
branch1.accept(82000.0);

DoubleSummaryStatistics branch2 = new DoubleSummaryStatistics();
branch2.accept(68000.0);
branch2.accept(91000.0);

branch1.combine(branch2);
System.out.println("Combined count: " + branch1.getCount());   // 4
System.out.println("Combined avg:   " + branch1.getAverage()); // 79000.0

What should a senior engineer know?

  • summaryStatistics() is parallel-safe because the combine() method lets partial results merge.
  • For exact money calculations, use BigDecimal reducing collectors, not summaryStatistics().
  • The statistics object is mutable: accept() adds values and combine() merges. Do not share it across threads without synchronisation outside a stream context.
  • Collectors.summarizingDouble() and stream.summaryStatistics() produce the same object type. The collector form is for groupingBy; the stream form is for standalone use.

What are common interview follow-up questions?

  • "How do you get these statistics per department?" Use groupingBy with summarizingDouble as the downstream.
  • "What is the time complexity?" O(n) for a single pass over n elements.
  • "Is it thread-safe?" Within a parallel stream, yes. Outside a stream, the object is not synchronised.

Visual Overview

DIAGRAM
  List<Employee>
  [Rajesh:75K, Priya:82K, Amit:68K, Kavita:91K, Neha:78K]
       │
       │  .mapToDouble(Employee::getSalaryAsDouble)
       ▼
  DoubleStream [75000, 82000, 68000, 91000, 78000]
       │
       │  .summaryStatistics()   (single pass)
       ▼
  ┌──────────────────────────────────┐
  │   DoubleSummaryStatistics        │
  │   count   = 5                    │
  │   sum     = 394000.0             │
  │   min     = 68000.0              │
  │   max     = 91000.0              │
  │   average = 78800.0              │
  └──────────────────────────────────┘

Key Takeaway

When a payroll report needs several numeric figures together, computing each with its own stream traverses the data multiple times. Instead, project salaries into a primitive DoubleStream with mapToDouble and call summaryStatistics(), which makes a single pass and returns a DoubleSummaryStatistics object holding count, sum, minimum, maximum, and average all at once.

This pairs well with Collectors.summarizingDouble() inside a groupingBy to compute per-department statistics in one pass. At the National Bank of India, one traversal for five figures is exactly the efficiency gain the dashboard team needed.

The trap to watch for is empty-stream behaviour: count, sum, and average are zero, but min and max come back as sentinel values (Double.MAX_VALUE and Double.MIN_VALUE). Always check getCount() before trusting the extremes.

Remember one sentence: summaryStatistics() gives count, sum, min, max, and average in one pass, and always check count before trusting min and max on an empty stream.

---

Share
Use ← → arrow keys to navigate