1. The Hook (The "Byte-Sized" Intro)
- In a Nutshell: Stream API processes data declaratively (what, not how). Not a data structure—sequence of elements from source (collection, array).
- Pipeline: source → intermediate ops (filter, map) → terminal op (collect, forEach).
- Lazy evaluation: Intermediate ops don't execute until terminal op.
- Immutable: Stream ops don't modify source.
- One-shot: Can't reuse stream after terminal op.
- Parallel: .parallelStream() for multi-core.
- Key: Replace imperative for-loops with functional pipelines!
Think of assembly line. Traditional loop = worker does everything (fetch part, inspect, assemble, pack). Stream = specialized stations (filter station, transform station, collect station). Each station does one thing. Declarative, composable, parallel!
2. Conceptual Clarity (The "Simple" Tier)
💡 The Analogy
- Source: Raw materials warehouse
- Intermediate operations: Processing stations (filter defects, paint, assemble)
- Terminal operation: Shipping dock (final collection)
- Lazy evaluation: Stations idle until truck arrives (terminal op triggers)
Stream Pipeline
graph LR
A[Collection] -->|stream| B[Stream Source]
B -->|filter| C[Intermediate]
C -->|map| D[Intermediate]
D -->|sorted| E[Intermediate]
E -->|collect| F[Terminal]
F --> G[Result]
style B fill:#F57C00
style C fill:#2E7D32
style D fill:#2E7D32
style E fill:#2E7D32
style F fill:#C2185B
3. Technical Mastery (The "Deep Dive")
Stream Characteristics
| Feature | Description |
|---|---|
| Not data structure | Doesn't store elements |
| Functional | Doesn't modify source |
| Lazy | Intermediate ops deferred |
| One-shot | Can't reuse after terminal op |
| Possibly unbounded | Stream.iterate() infinite |
| Parallelizable | .parallelStream() |
4. Interactive & Applied Code
java
import java.util.*;
import java.util.stream.*;
public class StreamBasicsDemo {
public static void main(String[] args) {
demonstrateCreatingStreams();
demonstrateLazyEvaluation();
demonstratePipeline();
demonstrateParallel();
}
// Creating streams
static void demonstrateCreatingStreams() {
System.out.println("=== CREATING STREAMS ===");
// From collection
List<String> list = Arrays.asList("a", "b", "c");
Stream<String> stream1 = list.stream();
// From array
String[] array = {"x", "y", "z"};
Stream<String> stream2 = Arrays.stream(array);
// From values
Stream<String> stream3 = Stream.of("1", "2", "3");
// Empty stream
Stream<String> empty = Stream.empty();
// Infinite streams
Stream<Integer> infinite = Stream.iterate(0, n -> n + 1); // 0,1,2,3...
Stream<Double> randoms = Stream.generate(Math::random);
// Primitive streams
IntStream ints = IntStream.range(1, 5); // 1,2,3,4
ints.forEach(System.out::println);
}
// Lazy evaluation
static void demonstrateLazyEvaluation() {
System.out.println("\n=== LAZY EVALUATION ===");
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
System.out.println("Creating stream pipeline...");
Stream<Integer> stream = numbers.stream()
.filter(n -> {
System.out.println(" filtering: " + n);
return n % 2 == 0;
})
.map(n -> {
System.out.println(" mapping: " + n);
return n * n;
});
System.out.println("Pipeline created, but not executed yet!");
System.out.println("Calling terminal operation...");
List<Integer> result = stream.collect(Collectors.toList());
System.out.println("Result: " + result);
}
// Complete pipeline example
static void demonstratePipeline() {
System.out.println("\n=== STREAM PIPELINE ===");
List<String> words = Arrays.asList(
"apple", "banana", "cherry", "date",
"elderberry", "fig", "grape"
);
// ❌ BEFORE: Imperative (verbose)
List<String> result1 = new ArrayList<>();
for (String word : words) {
if (word.length() > 5) {
String upper = word.toUpperCase();
result1.add(upper);
}
}
System.out.println("Imperative: " + result1);
// ✅ AFTER: Declarative (concise)
List<String> result2 = words.stream()
.filter(w -> w.length() > 5) // Keep long words
.map(String::toUpperCase) // Transform to uppercase
.collect(Collectors.toList()); // Collect to list
System.out.println("Declarative: " + result2);
}
// Parallel streams
static void demonstrateParallel() {
System.out.println("\n=== PARALLEL STREAMS ===");
List<Integer> numbers = IntStream.rangeClosed(1, 1000)
.boxed()
.collect(Collectors.toList());
// Sequential
long start = System.currentTimeMillis();
long sum1 = numbers.stream()
.mapToLong(Integer::longValue)
.sum();
long seq = System.currentTimeMillis() - start;
// Parallel
start = System.currentTimeMillis();
long sum2 = numbers.parallelStream()
.mapToLong(Integer::longValue)
.sum();
long par = System.currentTimeMillis() - start;
System.out.println("Sequential: " + seq + "ms");
System.out.println("Parallel: " + par + "ms");
System.out.println("Speedup: " + (seq / (double) par) + "x");
}
}5. The Comparison & Decision Layer
| Imperative Loop | Stream API |
|---|---|
| How to do it | What to do |
| Mutable state | Immutable |
| Sequential only | Parallel easily |
| Verbose | Concise |
| Error-prone | Safer |
6. The "Interview Corner" (The Edge)
The "Killer" Interview Question: "Why can't you reuse a stream?" Answer: Streams are one-shot—terminal op consumes stream!
java
Stream<String> stream = list.stream();
stream.forEach(System.out::println); // ✅ OK: first use
stream.forEach(System.out::println); // ❌ ERROR: stream already used!
// IllegalStateException: stream has already been operated upon or closed
// Solution: Create new stream
list.stream().forEach(System.out::println);
list.stream().count(); // ✅ New streamPro-Tip: Sequential vs Parallel:
java
// Sequential (default)
list.stream().filter(...).collect(...);
// Parallel (multi-core)
list.parallelStream().filter(...).collect(...);
// When to use parallel?
// ✅ DO: Large data (10K+ elements), CPU-intensive ops
// ❌ DON'T: Small data, I/O ops, stateful operations