Skip to content

Commit 19ea880

Browse files
committed
feat(streams): add EvaluationStreams demo to illustrate lazy vs eager execution in Java Streams
What - Introduced `EvaluationStreams.java` with a clear example contrasting intermediate and terminal operations. - Demonstrated how **filter()** (intermediate op) sets up a pipeline without executing immediately. - Added debug prints inside filter to visualize when evaluation actually occurs. - Showed lifecycle: 1. Stream creation with `filter()`. 2. Lazy setup → no filtering happens before terminal op. 3. Execution triggered by `collect()` (terminal op). 4. Stream consumed → cannot be reused. Why - Developers often misunderstand why nothing happens when only intermediate operations are chained. - This demo clarifies: - Intermediate ops = **lazy** (build pipeline, no execution). - Terminal ops = **eager** (trigger evaluation, consume stream). - Provides a teaching example for the evaluation strategy of Java Streams. Logic 1. **Stream creation** - `names.stream().filter(...)` defines a pipeline. - filter has logic (`name.length() > 3`) with side-effect print `"Filtering: <name>"`. - No output yet because evaluation is deferred. 2. **Before terminal operation** - Printed message `"Before terminal operation"`. - Confirms that intermediate ops didn’t run yet. 3. **Terminal operation (`collect`)** - `collect(Collectors.toList())` executes the pipeline. - At this point, filtering executes sequentially for each element: ``` Filtering: Alice Filtering: Bob Filtering: Charlie Filtering: David ``` - Result: `["Alice", "Charlie", "David"]`. 4. **After terminal operation** - Printed `"After terminal operation"` and result list. - Demonstrates consumption → stream is closed and cannot be reused. Real-world applications - Debugging pipelines: printing inside filters/peeks to trace lazy evaluation. - Teaching functional/declarative style vs imperative loops. - Understanding performance benefits: pipelines are only executed when needed. - Production optimization: allows developers to chain multiple filters/maps without cost until terminal op executes. Notes - Intermediate ops: lazy, return new streams (e.g., filter, map, distinct, sorted). - Terminal ops: eager, consume streams (e.g., collect, forEach, reduce, count). - Once a terminal operation runs, reusing the same stream throws `IllegalStateException`. Signed-off-by: https://github.com/Someshdiwan <[email protected]>
1 parent 6ad5f4c commit 19ea880

File tree

1 file changed

+128
-0
lines changed

1 file changed

+128
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
Understanding Terminal Operations and Intermediate operations in Java Streams.
2+
3+
In Java Streams, operations are classified into two categories:
4+
5+
1. Intermediate Operations – Return a new stream and are lazy (not executed until a terminal operation is called).
6+
2. Terminal Operations – Produce a result or a side effect, consuming the stream in the process.
7+
After a terminal operation, the stream cannot be reused.
8+
9+
---
10+
11+
Explanation of Your Code
12+
13+
import java.util.Arrays;
14+
import java.util.List;
15+
import java.util.stream.Collectors;
16+
import java.util.stream.Stream;
17+
18+
public class EvaluationStreams {
19+
public static void main(String[] args) {
20+
List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David");
21+
22+
Stream<String> stream = names.stream()
23+
.filter(name -> {
24+
System.out.println("Filtering: " + name);
25+
return name.length() > 3;
26+
});
27+
28+
System.out.println("Before terminal operation");
29+
30+
List<String> result = stream.collect(Collectors.toList());
31+
32+
System.out.println("After terminal operation");
33+
System.out.println(result);
34+
}
35+
}
36+
37+
---
38+
39+
Step-by-Step Execution Flow:
40+
41+
1. Creating the Stream:
42+
43+
Stream<String> stream = names.stream().filter(name -> { ... });
44+
45+
- The stream is created from the names list.
46+
- filter() is an intermediate operation – it sets up a filtering condition but does not execute immediately (lazy evaluation).
47+
- No output appears yet because intermediate operations are not executed until a terminal operation is called.
48+
49+
---
50+
51+
2. Before Terminal Operation
52+
53+
System.out.println("Before terminal operation");
54+
55+
Output:
56+
57+
Before terminal operation
58+
59+
- Since the stream is lazy, no filtering has happened yet.
60+
61+
---
62+
63+
3. Triggering the Terminal Operation:
64+
65+
List<String> result = stream.collect(Collectors.toList());
66+
67+
- collect() is a terminal operation
68+
– it forces the stream to be processed.
69+
- Now, the filtering happens, and the condition (name.length() > 3) is applied.
70+
71+
Outputs during filtering:
72+
73+
Filtering: Alice
74+
Filtering: Bob
75+
Filtering: Charlie
76+
Filtering: David
77+
78+
Filtered values: "Alice", "Charlie", "David" (length > 3)
79+
80+
---
81+
82+
4. After Terminal Operation:
83+
84+
System.out.println("After terminal operation");
85+
System.out.println(result);
86+
87+
Output:
88+
89+
After terminal operation
90+
[Alice, Charlie, David]
91+
92+
- The stream is consumed – you cannot reuse it.
93+
94+
---
95+
96+
Key Characteristics of Terminal Operations:
97+
98+
1. Trigger Stream Processing: Without a terminal operation, intermediate operations (like filter(), map()) will not execute.
99+
100+
2. Consume the Stream: Once a terminal operation is called, the stream is closed and cannot be reused.
101+
102+
3. Return a Result or Side Effect: Produce a value (e.g., collect(), count()) or a side effect (e.g., forEach()).
103+
104+
---
105+
106+
Common Terminal Operations:
107+
108+
| Operation | Description | Example |
109+
|-----------------------|-------------------------------------------------|---------------------------------------------------------------|
110+
| collect() | Collects elements into a collection (List, Set) | List<Integer> list = stream.collect(Collectors.toList()); |
111+
| forEach() | Iterates through each element (side effect) | stream.forEach(System.out::println); |
112+
| reduce() | Combines elements into a single value | int sum = stream.reduce(0, Integer::sum); |
113+
| count() | Counts the number of elements | long count = stream.count(); |
114+
| anyMatch() | Checks if any element matches a condition | boolean result = stream.anyMatch(x -> x > 5); |
115+
| allMatch() | Checks if all elements match a condition | boolean result = stream.allMatch(x -> x > 0); |
116+
| noneMatch() | Checks if no elements match a condition | boolean result = stream.noneMatch(x -> x < 0); |
117+
| findFirst() | Returns the first element (if present) | Optional<Integer> first = stream.findFirst(); |
118+
| findAny() | Returns any element (useful in parallel) | Optional<Integer> any = stream.findAny(); |
119+
| min() / max() | Returns the smallest or largest value | int min = stream.min(Integer::compareTo).get(); |
120+
| toArray() | Collects elements into an array | Integer[] arr = stream.toArray(Integer[]::new); |
121+
122+
---
123+
124+
Key Takeaways:
125+
126+
- Intermediate operations are lazy – they set up a pipeline.
127+
- Terminal operations trigger processing and consume the stream.
128+
- Once a terminal operation is called, the stream cannot be reused.

0 commit comments

Comments
 (0)