Definition
Converting to an IntStream of ages is a map-then-aggregate pattern.
- mapToInt – takes a
ToIntFunctionand returns anIntStream;Employee::getAgeis the function. - sum() – adds all ages and returns a primitive
int. - average() – returns an
OptionalDoublebecause an empty stream has no average. - max() and min() – return an
OptionalInt, empty when the stream has no elements.
Because each aggregation is a terminal operation, you either recreate the stream per call or use summaryStatistics() once.
At National Bank of India, Pankaj asks Rajesh for four numbers about a team: the combined age, the average age, the oldest, and the youngest. Rajesh first copies just the ages onto a clean strip of paper (mapToInt(Employee::getAge)), ignoring names and salaries. From that strip he can add them, average them, and spot the highest and lowest at a glance. If the strip is blank because the team is empty, there is a total of zero but no meaningful average or extremes, which is why those three answers come wrapped as optionals.
Understanding the Concepts
How do you convert a List to an IntStream of ages?
Call stream() on the list and then mapToInt(Employee::getAge). The method reference is a ToIntFunction, so each employee is reduced to an int age and the pipeline becomes an IntStream.
package com.bank.hr;
import java.util.List;
import java.util.stream.IntStream;
public class AgeExtractor {
public static IntStream ageStream(List<Employee> employees) {
return employees.stream().mapToInt(Employee::getAge);
}
}
Why use a method reference instead of a lambda?
Employee::getAge and e -> e.getAge() compile to the same thing, but the method reference states the intent directly and has less noise. Both satisfy the ToIntFunction that mapToInt expects.
// Method reference: concise, expresses "get the age"
employees.stream().mapToInt(Employee::getAge);
// Equivalent lambda: more characters, same behavior
employees.stream().mapToInt(e -> e.getAge());
How do you compute sum, average, max, and min?
Each is a terminal operation on IntStream. Remember that a stream can be consumed only once, so build a fresh stream for each call, or use summaryStatistics() to get everything at once.
package com.bank.hr;
import java.util.List;
import java.util.OptionalDouble;
import java.util.OptionalInt;
public class AgeAggregates {
public static void main(String[] args) {
List<Employee> team = List.of(
new Employee(1L, "Rajesh", 35),
new Employee(2L, "Priya", 29),
new Employee(3L, "Pankaj", 42));
int sum = team.stream().mapToInt(Employee::getAge).sum();
OptionalDouble avg = team.stream().mapToInt(Employee::getAge).average();
OptionalInt max = team.stream().mapToInt(Employee::getAge).max();
OptionalInt min = team.stream().mapToInt(Employee::getAge).min();
System.out.println("sum = " + sum);
System.out.println("average = " + avg.getAsDouble());
System.out.println("max = " + max.getAsInt());
System.out.println("min = " + min.getAsInt());
}
}
sum = 106
average = 35.333333333333336
max = 42
min = 29
What do these methods return, and why the difference?
sum() always has an answer (zero when empty), so it returns a plain int. average(), max(), and min() have no answer for an empty stream, so they return optionals that force the caller to decide what to do when there is no data.
| Method | Return type | Empty stream result |
|--------|-------------|---------------------|
| sum() | int | 0 |
| average() | OptionalDouble | OptionalDouble.empty() |
| max() | OptionalInt | OptionalInt.empty() |
| min() | OptionalInt | OptionalInt.empty() |
List<Employee> none = List.of();
System.out.println(none.stream().mapToInt(Employee::getAge).sum()); // 0
System.out.println(none.stream().mapToInt(Employee::getAge).max().isPresent()); // false
Why can you not reuse one IntStream for all four results?
A stream is consumed by its first terminal operation. Calling a second terminal operation on the same stream throws IllegalStateException.
// WRONG: reusing a consumed stream
IntStream ages = team.stream().mapToInt(Employee::getAge);
int sum = ages.sum(); // consumes the stream
double avg = ages.average().getAsDouble(); // throws IllegalStateException
// CORRECT option A: a fresh stream per aggregation
int sum = team.stream().mapToInt(Employee::getAge).sum();
double avg = team.stream().mapToInt(Employee::getAge).average().orElse(0);
How do you compute all four in a single pass?
summaryStatistics() returns an IntSummaryStatistics with count, sum, min, average, and max after one traversal, which is more efficient than four separate streams.
package com.bank.hr;
import java.util.IntSummaryStatistics;
import java.util.List;
public class AgeSummary {
public static void main(String[] args) {
List<Employee> team = List.of(
new Employee(1L, "Rajesh", 35),
new Employee(2L, "Priya", 29),
new Employee(3L, "Pankaj", 42));
IntSummaryStatistics s = team.stream()
.mapToInt(Employee::getAge)
.summaryStatistics();
System.out.printf("sum=%d avg=%.2f max=%d min=%d%n",
s.getSum(), s.getAverage(), s.getMax(), s.getMin());
}
}
sum=106 avg=35.33 max=42 min=29
What are the common mistakes?
The first mistake is unwrapping an optional without handling the empty case.
// WRONG: throws NoSuchElementException if the team is empty
int oldest = team.stream().mapToInt(Employee::getAge).max().getAsInt();
// CORRECT: provide a default or branch on presence
int oldest = team.stream().mapToInt(Employee::getAge).max().orElse(0);
The second mistake is computing a sum or average with boxed Integer and reduce when IntStream already offers direct methods.
// WRONG style: boxing path for a plain average
double avg = team.stream().map(Employee::getAge)
.collect(java.util.stream.Collectors.averagingInt(a -> a));
// CORRECT: primitive stream, no boxing
double avg = team.stream().mapToInt(Employee::getAge).average().orElse(0);
What are the edge cases?
A single-element stream returns that element for both max and min and itself as the average. A stream with very large ages cannot realistically overflow for human ages, but summing arbitrary large ints should use mapToLong for safety.
List<Employee> one = List.of(new Employee(9L, "Priya", 29));
System.out.println(one.stream().mapToInt(Employee::getAge).max().getAsInt()); // 29
System.out.println(one.stream().mapToInt(Employee::getAge).min().getAsInt()); // 29
System.out.println(one.stream().mapToInt(Employee::getAge).average().getAsDouble()); // 29.0
When should you use separate calls versus summaryStatistics?
Use separate calls when you need only one or two aggregates and clarity matters. Use summaryStatistics() when you need several at once or when the source is expensive to traverse.
// Only the average is needed: a single direct call reads best
double avg = team.stream().mapToInt(Employee::getAge).average().orElse(0);
// Several needed and traversal is costly: one pass
IntSummaryStatistics all = team.stream().mapToInt(Employee::getAge).summaryStatistics();
What should a senior engineer know?
- Terminal operation semantics.
sum,average,max,min, andsummaryStatisticsall consume the stream; plan to rebuild or use one pass. - Primitive advantage.
mapToIntavoids boxing, so the aggregation works directly on raw ints. - Rounding of average.
average()returns adouble; format it for display rather than storing it where exact values matter. - Parallelism. These reductions are associative and parallelize safely, and
summaryStatisticsmerges partial statistics correctly across threads.
What are common interview follow-up questions?
- "What does
sum()return and what aboutaverage()?" Answer:sum()returnsint;average()returnsOptionalDouble. - "Why can you not call
sum()and thenaverage()on the same stream?" Answer: a stream is consumed by its first terminal operation and throwsIllegalStateExceptionon reuse. - "How do you get all four in one pass?" Answer: use
summaryStatistics(). - "Why a method reference over a lambda?" Answer: it is more concise and expresses intent while compiling to the same
ToIntFunction.
Visual Overview
List<Employee>
[ (Rajesh,35), (Priya,29), (Pankaj,42) ]
│
│ stream().mapToInt(Employee::getAge)
▼
IntStream [ 35, 29, 42 ]
│
├── sum() -> int 106
├── average() -> OptionalDouble 35.33
├── max() -> OptionalInt 42
└── min() -> OptionalInt 29
One stream, one terminal op
IntStream ──sum()──▶ consumed
└── average() X IllegalStateException (already used)
Rebuild per call OR one pass
stream().mapToInt(..).sum() IntSummaryStatistics s =
stream().mapToInt(..).average() stream().mapToInt(..).summaryStatistics();
stream().mapToInt(..).max() s.getSum()/getAverage()/getMax()/getMin()
stream().mapToInt(..).min()
Key Takeaway
When Pankaj asks Rajesh for the combined, average, oldest, and youngest age of a team, Rajesh first copies just the ages onto a clean strip with mapToInt(Employee::getAge). That turns a List into an IntStream of raw ints, dropping the names and salaries that do not matter for this question. The method reference Employee::getAge reads as plain intent and is the same ToIntFunction a lambda would produce.
From that IntStream the four answers come from four terminal operations. sum() always has an answer and returns a plain int. average(), max(), and min() return optionals, because an empty team has a total of zero but no meaningful average or extremes. That is the API being honest instead of inventing a value, so you unwrap with orElse or check presence.
The catch is that a stream is consumed by its first terminal operation. You cannot call sum() and then average() on the same IntStream; the second call throws IllegalStateException. So either build a fresh stream for each aggregate, or ask for all of them at once with summaryStatistics(), which walks the data a single time and returns count, sum, min, average, and max together.
The primitive path matters too. Because mapToInt produces raw ints, the arithmetic runs without boxing each age into an Integer, which is both faster and lighter than the boxed Stream route.
In our banking work the habit is: map to the primitive you need, decide whether you want one aggregate or several, and when it is several, prefer the single-pass summaryStatistics.
Remember one sentence: mapToInt(Employee::getAge) gives an IntStream where sum() returns an int and average(), max(), min() return optionals, and each terminal call needs its own stream.
---