1. The Hook (The "Byte-Sized" Intro)
In a Nutshell: Terminal operations trigger stream execution, consume stream
: forEach() (iterate), collect() (gather to collection), reduce() (combine to single value), count() (size), min/max() (extremes), anyMatch/allMatch() (test all), findFirst/findAny() (retrieve element). Collectors (Collectors class): toList/toSet/toMap(), joining() (concat strings), groupingBy() (group into Map), partitioningBy() (split boolean), summarizingInt() (statistics). Key: Terminal ops return result, close stream!
Think of harvest season. Stream operations = growing crops (filter weeds, water plants). Terminal operation = harvesting! collect() = gather into baskets. reduce() = blend into juice. forEach() = sell each fruit individually. Can't harvest twice from same field!
2. Conceptual Clarity (The "Simple" Tier)
💡 The Analogy
- reduce(): Blender (many fruits → one smoothie)
- collect(): Storage containers (organize harvest)
- groupingBy(): Sorting bins (by type/size)
- forEach(): Distribute to customers
3. Technical Mastery (The "Deep Dive")
Terminal Operations
| Operation | Returns | Purpose |
|---|---|---|
| forEach | void | Side effects |
| collect | R | Mutable reduction to collection |
| reduce | Optional<T> | Immutable reduction |
| count | long | Count elements |
| anyMatch | boolean | Any element matches? |
| allMatch | boolean | All elements match? |
| findFirst | Optional<T> | First element |
| min/max | Optional<T> | Extreme values |
4. Interactive & Applied Code
import java.util.*;
import java.util.stream.*;
public class TerminalOpsCollectorsDemo {
public static void main(String[] args) {
demonstrateTerminalOps();
demonstrateCollectors();
demonstrateGrouping();
demonstrateStatistics();
}
// Terminal operations
static void demonstrateTerminalOps() {
System.out.println("=== TERMINAL OPERATIONS ===");
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// forEach
numbers.stream().forEach(n -> System.out.print(n + " "));
System.out.println();
// count
long count = numbers.stream().filter(n -> n % 2 == 0).count();
System.out.println("Even count: " + count);
// reduce (sum)
Optional<Integer> sum = numbers.stream().reduce((a, b) -> a + b);
sum.ifPresent(s -> System.out.println("Sum: " + s));
// reduce with identity
int product = numbers.stream().reduce(1, (a, b) -> a * b);
System.out.println("Product: " + product);
// min/max
Optional<Integer> min = numbers.stream().min(Integer::compareTo);
Optional<Integer> max = numbers.stream().max(Integer::compareTo);
min.ifPresent(m -> System.out.println("Min: " + m));
max.ifPresent(m -> System.out.println("Max: " + m));
// anyMatch, allMatch, noneMatch
boolean hasEven = numbers.stream().anyMatch(n -> n % 2 == 0);
boolean allPositive = numbers.stream().allMatch(n -> n > 0);
boolean noneNegative = numbers.stream().noneMatch(n -> n < 0);
System.out.println("Has even: " + hasEven);
System.out.println("All positive: " + allPositive);
// findFirst, findAny
Optional<Integer> first = numbers.stream().filter(n -> n > 5).findFirst();
first.ifPresent(f -> System.out.println("First >5: " + f));
}
// Collectors
static void demonstrateCollectors() {
System.out.println("\n=== COLLECTORS ===");
List<String> words = Arrays.asList("apple", "banana", "cherry", "date", "elderberry");
// toList
List<String> list = words.stream()
.filter(w -> w.length() > 5)
.collect(Collectors.toList());
System.out.println("List: " + list);
// toSet
Set<Integer> lengths = words.stream()
.map(String::length)
.collect(Collectors.toSet());
System.out.println("Unique lengths: " + lengths);
// toMap
Map<String, Integer> map = words.stream()
.collect(Collectors.toMap(
w -> w, // Key
String::length // Value
));
System.out.println("Map: " + map);
// joining
String joined = words.stream()
.collect(Collectors.joining(", ", "[", "]"));
System.out.println("Joined: " + joined);
// joining with transformation
String upperJoined = words.stream()
.map(String::toUpperCase)
.collect(Collectors.joining(" | "));
System.out.println("Upper joined: " + upperJoined);
}
// Grouping and Partitioning
static void demonstrateGrouping() {
System.out.println("\n=== GROUPING ===");
List<String> words = Arrays.asList(
"apple", "apricot", "banana", "blueberry",
"cherry", "cranberry", "date"
);
// Group by length
Map<Integer, List<String>> byLength = words.stream()
.collect(Collectors.groupingBy(String::length));
System.out.println("By length: " + byLength);
// Group by first letter
Map<Character, List<String>> byFirstLetter = words.stream()
.collect(Collectors.groupingBy(w -> w.charAt(0)));
System.out.println("By first letter: " + byFirstLetter);
// Group and count
Map<Integer, Long> countByLength = words.stream()
.collect(Collectors.groupingBy(
String::length,
Collectors.counting()
));
System.out.println("Count by length: " + countByLength);
// Partition by predicate (boolean)
Map<Boolean, List<String>> partitioned = words.stream()
.collect(Collectors.partitioningBy(w -> w.length() > 5));
System.out.println("Long words: " + partitioned.get(true));
System.out.println("Short words: " + partitioned.get(false));
}
// Statistical collectors
static void demonstrateStatistics() {
System.out.println("\n=== STATISTICS ===");
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// summingInt
int sum = numbers.stream()
.collect(Collectors.summingInt(Integer::intValue));
System.out.println("Sum: " + sum);
// averagingInt
double avg = numbers.stream()
.collect(Collectors.averagingInt(Integer::intValue));
System.out.println("Average: " + avg);
// summarizingInt (get all statistics at once)
IntSummaryStatistics stats = numbers.stream()
.collect(Collectors.summarizingInt(Integer::intValue));
System.out.println("Statistics: " + stats);
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());
}
}
// Real-world example: Employee data processing
class EmployeeDataDemo {
static class Employee {
String name;
String dept;
int salary;
Employee(String name, String dept, int salary) {
this.name = name;
this.dept = dept;
this.salary = salary;
}
}
public static void main(String[] args) {
List<Employee> employees = Arrays.asList(
new Employee("Alice", "Engineering", 80000),
new Employee("Bob", "Sales", 60000),
new Employee("Carol", "Engineering", 90000),
new Employee("David", "Sales", 70000),
new Employee("Eve", "Engineering", 85000)
);
// Group by department
Map<String, List<Employee>> byDept = employees.stream()
.collect(Collectors.groupingBy(e -> e.dept));
System.out.println("By department: " + byDept.keySet());
// Average salary by department
Map<String, Double> avgSalary = employees.stream()
.collect(Collectors.groupingBy(
e -> e.dept,
Collectors.averagingInt(e -> e.salary)
));
System.out.println("Avg salary by dept: " + avgSalary);
// Highest paid employee
Optional<Employee> highest = employees.stream()
.max(Comparator.comparingInt(e -> e.salary));
highest.ifPresent(e ->
System.out.println("Highest paid: " + e.name + " - $" + e.salary));
// Total payroll
int totalPayroll = employees.stream()
.mapToInt(e -> e.salary)
.sum();
System.out.println("Total payroll: $" + totalPayroll);
}
}5. The Comparison & Decision Layer
| Task | Method | Example |
|---|---|---|
| To List | toList() | collect(Collectors.toList()) |
| Join strings | joining() | joining(", ") |
| Group by | groupingBy() | groupingBy(String::length) |
| Split boolean | partitioningBy() | partitioningBy(n -> n > 5) |
| Sum | summingInt() | summingInt(Integer::intValue) |
| Statistics | summarizingInt() | summarizingInt(...) |
6. The "Interview Corner" (The Edge)
The "Killer" Interview Question: "collect() vs reduce()?" Answer:
- collect(): Mutable reduction (modifies container)
- reduce(): Immutable reduction (creates new values)
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
// reduce(): Immutable (creates new Integer each time)
int sum = numbers.stream().reduce(0, (a, b) -> a + b);
// collect(): Mutable (modifies List)
List<Integer> evens = numbers.stream()
.filter(n -> n % 2 == 0)
.collect(Collectors.toList()); // Modifies ListUse collect() for collections, reduce() for primitives/single values!
Pro-Tip: Downstream collectors:
// Count elements in each group
Map<Integer, Long> counts = words.stream()
.collect(Collectors.groupingBy(
String::length, // Classifier
Collectors.counting() // Downstream collector
));