Stream API
TL;DR
- Stream API (Java 8) is a declarative, functional approach to processing collections of data.
- Instead of writing imperative
forloops (how to do it), you declare a pipeline of operations (what you want). - Streams are Lazy. Intermediate operations (
filter,map) don’t actually execute until a Terminal operation (collect,count) is invoked.
Concept
Before Java 8, if you wanted to find the names of all employees who earn over $100k, sort them alphabetically, and put them in a list, you had to write a massive, 15-line for loop with if statements and a custom Collections.sort() comparator.
Streams allow you to do this in a single, highly readable pipeline. You pipe the collection through a filter (removes low earners), then a map (extracts the names), then sorted (alphabetizes), and finally collect (packs them into a new List).
The beauty of Streams is laziness. If your terminal operation is .findFirst(), the stream will process elements one by one through the filter/map chain and instantly stop processing as soon as it finds a single match, saving massive CPU cycles.
Examples
import java.util.*;
import java.util.stream.Collectors;
class Employee {
String name;
int salary;
// constructors/getters
}
public class StreamDemo {
public static void main(String[] args) {
List<Employee> employees = Arrays.asList(
new Employee("Alice", 120000),
new Employee("Bob", 80000),
new Employee("Charlie", 150000)
);
// Declarative, Functional Pipeline
List<String> highEarners = employees.stream()
.filter(e -> e.getSalary() > 100000) // Keep > 100k
.map(Employee::getName) // Extract just the name
.sorted() // Sort alphabetically
.collect(Collectors.toList()); // Pack into a new List
System.out.println(highEarners); // Prints: [Alice, Charlie]
}
}
Interview Questions
Q: What is the difference between an Intermediate and a Terminal operation?
A: - Intermediate Operations (filter, map, sorted, limit): They always return another Stream. They are lazy; they do absolutely nothing when called. They just build up an execution plan.
- Terminal Operations (
collect,count,forEach,findAny): They return a concrete result (a List, an int, a boolean) or void. Invoking a terminal operation is what actually ignites the pipeline and causes data to flow through it. A stream can only have exactly one terminal operation at the very end.
Q: Can a Stream be reused?
A: No. Streams are strictly “single-use”. Once a terminal operation has been executed on a stream, the stream is considered closed and consumed. If you try to call another operation on that exact same stream instance, Java will throw an IllegalStateException: stream has already been operated upon or closed. You must generate a brand new stream from the source collection.