Streams API
Forget methods for a second. Think of a stream like a factory conveyor belt:
Raw materials → [Station 1] → [Station 2] → [Station 3] → Final Product
(your list) (filter) (transform) (sort) (collect)
Every station either:
● Removes items (filter, distinct, limit)
● Transforms items (map, flatMap)
● Reorders items (sorted)
● Terminates the belt and packages the result (collect, findFirst, count, reduce)
🏗️ The Blueprint — Always This Shape
result = source
.stream() // START the belt
// INTERMEDIATE — transforms items, returns a new stream
.filter(...) // keeps/removes items
.map(...) // shape-shifts each item
.sorted(...) // reorders
// TERMINAL — ends the belt, gives you your result
.collect(...); // package into List/Map/Set
👥 Our Dataset — We'll use this throughout
// Let's create a realistic Employee class
record Employee(String name, String dept, int salary, int age) {}
List<Employee> employees = [Link](
new Employee("Alice", "Engineering", 95000, 30),
new Employee("Bob", "Engineering", 72000, 45),
new Employee("Carol", "HR", 60000, 35),
new Employee("David", "HR", 65000, 28),
new Employee("Eve", "Engineering", 110000, 38),
new Employee("Frank", "Marketing", 55000, 25),
new Employee("Grace", "Marketing", 70000, 32)
);
🎯 Method 1: filter — The Gatekeeper
One question to ask yourself: "Do I want ALL items or just SOME?" If SOME → use filter.
// ❓ Problem: Get only Engineering employees
List<Employee> engineers = [Link]()
.filter(e -> [Link]().equals("Engineering")) // only let Engineers through
.collect([Link]());
// 👉 engineers = [Alice, Bob, Eve]
The lambda e -> [Link]().equals("Engineering") reads as:
"For each employee e, keep them IF this is true"
🎯 Method 2: map — The Shape-Shifter
One question: "Do I need the whole object, or just ONE thing from it?" If just ONE thing → use
map.
// ❓ Problem: Get just the names of all employees
List<String> names = [Link]()
.map(e -> [Link]()) // transform Employee → String
.collect([Link]());
// 👉 names = ["Alice", "Bob", "Carol", "David", "Eve", "Frank", "Grace"]
Think of map as: "Replace each item with something else"
[Employee] [Employee] [Employee]
↓ ↓ ↓
[String] [String] [String]
🔗 Chaining — The Real Power
Now combine them. Read it top to bottom like a sentence:
// ❓ Problem: Get names of all Engineering employees, sorted alphabetically
List<String> result = [Link]()
.filter(e -> [Link]().equals("Engineering")) // keep only Engineers
.map(e -> [Link]()) // grab just their names
.sorted() // sort A→Z
.collect([Link]());
// 👉 ["Alice", "Bob", "Eve"]
The thinking process:
1. What's my input? → List of Employees
2. Do I want all or some? → Some (Engineering only) → filter
3. Do I need the whole object or part of it? → Just names → map
4. Any ordering? → Yes, alphabetical → sorted
5. What's my output container? → List → collect(toList())
🎯 Method 3: sorted — The Organizer
// ❓ Problem: Get all employees sorted by salary (highest first)
List<Employee> sorted = [Link]()
.sorted([Link](Employee::salary).reversed())
.collect([Link]());
// 👉 [Eve(110k), Alice(95k), Bob(72k), Grace(70k), David(65k), Carol(60k), Frank(55k)]
🎯 Terminal Methods — Packaging the Result
// collect(toList()) → gives you a List
// collect(toSet()) → gives you a Set (no duplicates)
// count() → gives you a long (how many)
// findFirst() → gives you Optional<T> (first match)
// anyMatch() → gives you boolean (does ANY match?)
// allMatch() → gives you boolean (do ALL match?)
//❓ Does any employee earn over 100k?
boolean hasHighEarner = [Link]()
.anyMatch(e -> [Link]() > 100000); // true (Eve earns 110k)
//❓ How many employees are in HR?
long hrCount = [Link]()
.filter(e -> [Link]().equals("HR"))
.count(); // 2
🏆 The "Think in 5 Questions" Framework
Before writing a single line, ask yourself:
# Question → Method
1 Do I want ALL items or SOME? filter if SOME
2 Do I need the whole object or a PART? map if PART
3 Do I need it ORDERED? sorted
4 Are there DUPLICATES I want to remove? distinct
5 What should my OUTPUT look like? collect / count /
findFirst
🔥 Now You Try — 3 Challenges
Try solving these mentally first, then code them:
Challenge 1 (Easy): Get a list of salaries of all Marketing employees.
Challenge 2 (Medium): Get the name of the youngest employee in Engineering.
Challenge 3 (Hard): Group all employees by department (gives you a Map<String,
List<Employee>>).
Want me to:
● Walk through the challenges together step by step?
● Go deeper into flatMap, reduce, or groupingBy?
● Show you the answers to the challenges with full explanation?
Tell me where you want to go next! 🚀
Great question! Let me break every part down:
.mapToInt(Integer::intValue)
Your stream starts as Stream<Integer> (boxed objects). mapToInt() converts it into an
IntStream (raw primitives).
Stream<Integer> → .mapToInt(Integer::intValue) → IntStream
Integer::intValue is a method reference that simply unwraps each Integer object into a
plain int. You could also write it as:
.mapToInt(n -> n) // autoboxing does the same thing
Why bother converting? Because IntStream gives you access to math-specific methods like
sum(), average(), min(), max() — which a regular Stream<Integer> does NOT have.
.summaryStatistics()
This is a special terminal operation only available on primitive streams (IntStream,
LongStream, DoubleStream). It computes all five statistics in a single pass over the data
and bundles them into one object — instead of you having to iterate the list 5 separate times.
IntSummaryStatistics stats = ...summaryStatistics();
The IntSummaryStatistics object holds:
Method Returns
[Link]() sum of all elements
[Link]() smallest element
[Link]() largest element
[Link] returns double
ge()
[Link] how many
() elements
Why not just do each separately?
You could, but it's 5 stream passes:
int sum = [Link]().mapToInt(Integer::intValue).sum();
int min = [Link]().mapToInt(Integer::intValue).min().getAsInt();
int max = [Link]().mapToInt(Integer::intValue).max().getAsInt();
double avg = [Link]().mapToInt(Integer::intValue).average().getAsDouble();
long count = [Link]().count();
summaryStatistics() does all of this in one single pass — much more efficient.
The full flow:
List<Integer> [3, 7, 1, 9, 4, 6]
|
.stream()
|
Stream<Integer>
|
.mapToInt(Integer::intValue)
|
IntStream (primitives: 3, 7, 1, 9, 4, 6)
|
.summaryStatistics()
|
IntSummaryStatistics
├── count = 6
├── sum = 30
├── min = 1
├── max = 9
└── average = 5.0
One thing to note about getAverage() — it returns a double, not an int, because
averages are rarely whole numbers. And if the stream were empty, getMin() and getMax()
would return Integer.MAX_VALUE and Integer.MIN_VALUE respectively, while
getAverage() would return 0.0 — so always be careful with empty streams.