Terminal Operations
TL;DR
- Terminal Operations are the final step in a stream pipeline.
- They trigger the actual execution of the stream (lazy evaluation).
- They return a non-stream result (like a
List,int,boolean, orvoid). - Once a terminal operation completes, the stream is closed.
Concept
A Stream does nothing without a Terminal Operation. It is the engine that pulls data through the pipeline.
Terminal operations can be grouped into a few categories:
- Iterating:
forEach() - Matching:
anyMatch(),allMatch(),noneMatch()(These are short-circuiting). - Finding:
findFirst(),findAny()(Short-circuiting). - Reduction:
reduce(),count(),max(),min() - Collecting:
collect(),toArray()
Examples
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
public class TerminalOps {
public static void main(String[] args) {
List<String> fruits = Arrays.asList("apple", "banana", "cherry", "date");
// 1. Matching (Returns boolean)
boolean hasB = fruits.stream().anyMatch(f -> f.startsWith("b"));
System.out.println("Has B? " + hasB);
// 2. Finding (Returns Optional)
Optional<String> firstC = fruits.stream().filter(f -> f.startsWith("c")).findFirst();
// 3. Reduction (Returns long, Optional, etc)
long count = fruits.stream().count();
// 4. Collecting (Returns Collection)
List<String> upper = fruits.stream().map(String::toUpperCase).collect(Collectors.toList());
// 5. Iterating (Returns void)
fruits.stream().forEach(System.out::println);
}
}
Interview Questions
Q: What does “Short-Circuiting” mean for a terminal operation?
A: Short-circuiting terminal operations (like findFirst, anyMatch, allMatch) do not necessarily process the entire stream. For instance, if anyMatch(f -> f.startsWith("b")) finds “banana” as the second item, it immediately returns true and terminates the pipeline. It doesn’t bother looking at the rest of the stream.
Q: Can you execute two terminal operations on the same stream?
A: No. Attempting to do so will throw an IllegalStateException. Streams are consumed upon execution. If you need to perform multiple terminal operations on the same data source, you must call .stream() on the source collection multiple times to create a fresh pipeline each time.