Study guide
8.1 Stream Creation and Intermediate Operations
Oracle Java SE 21 Developer 1Z0-830
Track your progress as you read
Finished sections get a check mark.
1 Chapter 1: Java Basics, Primitive Types, and Operators
1.1 Program Structure, Primitives, Literals, Scopes, and var 1.2 Operators, Precedence, and Type Casting 1.3 Strings, Text Blocks, and StringBuilder 1.4 Date and Time APIs (java.time)
2 Chapter 2: Controlling Program Flow & Pattern Matching
2.1 Conditional Logic: if-else and Ternary Operators 2.2 Switch Statements and Switch Expressions 2.3 Pattern Matching for switch and instanceof 2.4 Loops and Branching Statements
3 Chapter 3: Object-Oriented Design & Class Architecture
3.1 Class Declarations, Constructors, and Object Lifecycle 3.2 Encapsulation and Access Modifiers 3.3 Inheritance, Method Overriding, and Polymorphism 3.4 Abstract Classes and Interfaces 3.5 Nested, Inner, Local, and Anonymous Classes 3.6 Method Overloading, Overload Resolution, and Varargs
4 Chapter 4: Modern Java Data Modeling: Records, Enums, and Sealed Types
4.1 Java Records, Canonical/Compact Constructors, and Component Accessors 4.2 Java Enums: Fields, Methods, and Abstract Constant-Specific Behavior 4.3 Sealed Classes and Interfaces: permits, final, non-sealed, and Hierarchy Architecture 4.4 Advanced Record Patterns (JEP 440), Nested Deconstruction, and Sealed Hierarchy Exhaustiveness
5 Chapter 5: Exception Handling and Annotations
5.1 Exception Hierarchy: Checked vs. Unchecked Exceptions 5.2 Exception Handling: try, catch, finally, and Multi-Catch 5.3 Resource Management: try-with-resources and AutoCloseable 5.4 Custom Exceptions and Best Practices 5.5 Java Annotations: Built-in, Meta-Annotations, and Custom Types
6 Chapter 6: Generics and the Collections Framework
6.1 Generics Fundamentals and Generic Methods 6.2 Wildcards, Type Bounds, and Type Erasure 6.3 Core Collections: Lists, Sets, Queues, and Maps 6.4 Sequenced Collections in Java 21 6.5 Sorting, Comparable, and Comparator 6.6 Arrays: Creation, Multidimensional Forms, and the Arrays Utility Class
7 Chapter 7: Functional Programming & Lambda Expressions
7.1 Built-in Functional Interfaces in java.util.function 7.2 Lambda Expressions, Variable Scopes, and Effectively Final 7.3 Method References: Four Varieties and Edge Cases 7.4 Working with java.util.Optional
8 Chapter 8: The Stream API and Data Pipelines
8.1 Stream Creation and Intermediate Operations 8.2 Terminal Operations and Reductions 8.3 Advanced Collectors, Grouping, Partitioning, and Teeing 8.4 Primitive Streams and Parallel Stream Processing
9 Chapter 9: Concurrency, Multithreading, and Virtual Threads
9.1 Thread Fundamentals, Creation, and Lifecycle 9.2 Thread Safety, Synchronization, and the Java Memory Model 9.3 Concurrency Utilities: ExecutorService, Callable, and Future 9.4 Concurrent Collections, Atomics, and Explicit Locks 9.5 Virtual Threads in Java 21 (Carrier Threads, Pinning, and Best Practices)
10 Chapter 10: The Java Platform Module System (JPMS)
10.1 Module Architecture and module-info.java Declarations 10.2 Module Directives: requires, exports, opens, provides, and uses 10.3 Modular Compilation, Packaging, and CLI Execution 10.4 Module Types, Migration Strategies, ServiceLoader SPI, and JLink
11 Chapter 11: Java I/O, NIO.2, and Object Serialization
11.1 Classic Java I/O Streams and Readers/Writers 11.2 Java NIO.2 File API: Path, Paths, and File System Operations 11.3 Advanced NIO.2 Stream Operations and Directory Traversal 11.4 Java Object Serialization and Record Serialization
12 Chapter 12: Implementing Localization and Internationalization
12.1 Locales and Resource Bundles 12.2 Formatting and Parsing Numbers, Currencies, Dates, Times, and Messages
8.1 Stream Creation and Intermediate Operations
Key Takeaways Explain This Topic AI
Streams represent lazy, declarative data pipelines divided into source creation, zero or more intermediate transformations, and a single terminal execution phase.
Intermediate operations are classified as stateless (filter, map, flatMap, peek) or stateful (sorted, distinct, limit, skip, takeWhile, dropWhile), with execution deferred until a terminal operation is invoked.
Java 9 introduced takeWhile and dropWhile for predicate-based stream boundary slicing, behaving deterministically on ordered streams and stopping evaluation immediately upon condition mismatch.
Streams are single-use pipelines; invoking a terminal operation or closing a stream consumes it permanently, causing any subsequent operation on that stream instance to throw an IllegalStateException.
Last updated: September 2026
Stream Creation and Intermediate Operations
The Stream API , introduced in Java 8 and continuously enhanced through Java 9 to Java 21, provides a functional, declarative approach to processing sequences of elements. Unlike collections, streams do not store data ; instead, they convey elements from a source (such as a collection, array, I/O channel, or generator function) through a pipeline of computational steps.
For the Oracle Certified Professional: Java SE 21 Developer (1Z0-830) exam, you must master the mechanics of stream construction, lazy evaluation, vertical pipeline processing, stateless versus stateful intermediate transformations, and stream boundary slicing ( takeWhile and dropWhile ).
1. The Stream Pipeline Architecture & Lifecycle
A stream pipeline consists of three fundamental components:
A Data Source : Supplies elements to the pipeline (e.g., List , Set , Map , array, file I/O, or generator function).
Zero or More Intermediate Operations : Transform the stream into another stream (e.g., filter , map , flatMap , sorted ). Intermediate operations are always lazy ; no computation occurs until the pipeline is activated.
Exactly One Terminal Operation : Triggers the traversal of the pipeline, processes the elements, produces a non-stream result (e.g., List , long , Optional , boolean ) or side-effect, and closes/consumes the stream. +-------------+ +-------------------------+ +--------------------+ +--------------------+
- | Data Source | ---> | Intermediate Op (Lazy) | ---> | Intermediate Op... | ---> | Terminal Operation |
- | (List/Array)| | filter(Predicate<T>) | | map(Function<T,R>)| | collect(toList()) |
- +-------------+ +-------------------------+ +--------------------+ +--------------------+
- │
- Execution Triggered Eagerly <────────────────────────┘
- Lazy Evaluation & Vertical Pipelining
Java streams execute vertically (element-by-element through all chained operations) rather than horizontally (evaluating each operation across the entire dataset before moving to the next). This vertical fusion minimizes intermediate memory allocations and allows short-circuiting operations to stop pipeline execution early. List<String> names = List.of("Alexander", "Brian", "Charles", "David", "Edward");
- String result = names.stream()
- .filter(s -> {
- System.out.println("filter: " + s);
- return s.length() > 5;
- })
- .map(s -> {
- System.out.println("map: " + s);
- return s.toUpperCase();
- })
- .findFirst()
- .orElse("NONE");
// Console Output demonstrates vertical execution (Brian, Charles, David, Edward are never mapped):
- // filter: Alexander
- // map: Alexander
Important
The Single-Use Rule : A stream instance can only be operated upon (traversed via a terminal operation) once . Once a terminal operation has been executed or the stream has been closed, attempting to invoke any intermediate or terminal operation on that same Stream reference throws java.lang.IllegalStateException: stream has already been operated upon or closed .
2. Stream Creation Factories & Data Sources
Java provides multiple mechanisms to construct both finite and infinite streams:
- Standard Collection & Array Sources // 1. From Collections
- List<String> list = List.of("Java", "Kotlin", "Scala");
- Stream<String> s1 = list.stream();
- Stream<String> s2 = list.parallelStream();
- // 2. From Arrays
- String[] array = {"Alpha", "Beta", "Gamma", "Delta"};
- Stream<String> s3 = Arrays.stream(array);
- Stream<String> s4 = Arrays.stream(array, 1, 3); // Sub-array [1, 3) -> "Beta", "Gamma"
- // 3. From Static Factory Methods
- Stream<String> s5 = Stream.of("One", "Two", "Three");
Stream<String> s6 = Stream.ofNullable(null); // Java 9: Produces Stream.empty() instead of NullPointerException
- Stream<String> s7 = Stream.empty();
- Infinite & Generator Streams
Infinite streams generate elements on demand. They must typically be bounded using short-circuiting intermediate operations like limit() before triggering a terminal operation. // Stream.generate(Supplier<T>)
Stream<Double> randoms = Stream.generate(Math::random).limit(5);
- // Stream.iterate(T seed, UnaryOperator<T> f) - Infinite
- Stream<Integer> evens = Stream.iterate(0, n -> n + 2).limit(10);
- // Stream.iterate(T seed, Predicate<T> hasNext, UnaryOperator<T> f) - Java 9 Finite (like a for-loop)
- Stream<Integer> finiteIterate = Stream.iterate(1, n -> n <= 100, n -> n * 2);
- // Yields: 1, 2, 4, 8, 16, 32, 64
- I/O and Character Sources // From Files (AutoCloseable: Always manage with try-with-resources!)
- try (Stream<String> lines = Files.lines(Path.of("application.log"))) {
- lines.filter(line -> line.contains("ERROR"))
- .forEach(System.out::println);
- }
- // From String characters (yields IntStream of UTF-16 code units or code points)
- IntStream chars = "Java 21".chars();
- IntStream codePoints = "Java 21".codePoints();
- // From Regular Expressions
- Pattern pattern = Pattern.compile(",\\s*");
- Stream<String> tokens = pattern.splitAsStream("apple, orange, banana, grape");
- // Concatenating Streams
- Stream<String> concatenated = Stream.concat(Stream.of("A", "B"), Stream.of("C", "D"));
- // Stream Builder
- Stream<String> built = Stream.<String>builder()
- .add("First")
- .add("Second")
- .build();
- 3. Stateless Intermediate Operations
Stateless intermediate operations process each element independently without retaining state or memory of previously processed elements. They are highly efficient and scale linearly in parallel pipelines.
Operation Method Signature Description
filter Stream<T> filter(Predicate<? super T> predicate) Retains elements matching the given boolean predicate.
map <R> Stream<R> map(Function<? super T, ? extends R> mapper) Performs 1-to-1 element transformation.
flatMap <R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper) Performs 1-to-N transformation and flattens nested streams into a single composite stream.
mapMulti <R> Stream<R> mapMulti(BiConsumer<? super T, ? super Consumer<R>> mapper) Java 16+ low-overhead imperative alternative to flatMap for replacing elements with zero or more items.
peek Stream<T> peek(Consumer<? super T> action) Executes an action on each element as it passes through the pipeline; intended for debugging.
unordered Stream<T> unordered() Relaxes ordering constraints on the pipeline to allow performance optimizations in parallel streams.
- Deep Dive: map vs. flatMap vs. mapMulti List<List<String>> nested = List.of(
- List.of("A", "B"),
- List.of("C", "D"),
- List.of("E")
- );
- // map: Results in Stream<List<String>> (does not flatten)
- Stream<List<String>> mapped = nested.stream().map(l -> l);
- // flatMap: Flattens into Stream<String>
- List<String> flatResult = nested.stream()
- .flatMap(Collection::stream)
- .map(String::toLowerCase)
- .toList(); // ["a", "b", "c", "d", "e"]
- // mapMulti (Java 16+): Avoids creating intermediate Stream instances
- List<String> multiResult = List.of("hello world", "stream api").stream()
- .<String>mapMulti((str, consumer) -> {
- for (String word : str.split(" ")) {
- consumer.accept(word.toUpperCase());
- }
- })
- .toList(); // ["HELLO", "WORLD", "STREAM", "API"]
Warning
The peek() Pitfall on 1Z0-830 : peek() is designed solely to support debugging. Never write business logic inside peek() . Starting in Java 9, stream implementations may optimize away intermediate operations if the terminal operation does not require element evaluation (such as Stream.of("a", "b").peek(System.out::print).count() ), resulting in peek() never executing!
4. Stateful Intermediate Operations
Stateful intermediate operations require knowledge of previously processed elements or must buffer the entire dataset before emitting elements downstream.
Operation Method Signature Stateful Behavior & Constraints
distinct() Stream<T> distinct() Uses equals() and hashCode() to filter out duplicates; retains state of seen elements.
sorted() Stream<T> sorted() ; Stream<T> sorted(Comparator<? super T> comp) Buffers all upstream elements before sorting; requires comparable elements or explicit Comparator .
limit(n) Stream<T> limit(long maxSize) Short-circuiting; truncates the stream to at most maxSize elements.
skip(n) Stream<T> skip(long n) Discards the first n elements; emits remaining elements.
takeWhile(p) Stream<T> takeWhile(Predicate<? super T> p) Java 9: Takes elements as long as predicate matches; stops immediately on first mismatch in ordered streams.
dropWhile(p) Stream<T> dropWhile(Predicate<? super T> p) Java 9: Drops elements while predicate matches; once false is encountered, emits all remaining elements.
takeWhile() vs. dropWhile() (Java 9+)
takeWhile and dropWhile operate on stream boundaries based on an element condition. Their behavior is critically dependent on stream order : List<Integer> numbers = List.of(2, 4, 6, 7, 8, 10, 12);
- // takeWhile: Stops at 7 (first element where n % 2 == 0 is false)
- List<Integer> taken = numbers.stream()
- .takeWhile(n -> n % 2 == 0)
- .toList();
- // Output: [2, 4, 6]
- // dropWhile: Drops 2, 4, 6; starts emitting at 7 and emits everything thereafter (even 8, 10, 12)
- List<Integer> dropped = numbers.stream()
- .dropWhile(n -> n % 2 == 0)
- .toList();
- // Output: [7, 8, 10, 12]
- takeWhile vs. filter Comparison
filter(p) evaluates every element in the stream against the predicate (unless bounded by limit ).
takeWhile(p) halts evaluation at the first failure in an ordered stream, making it safe for ordered infinite streams where a condition eventually fails. // Safe on infinite stream because takeWhile short-circuits:
- List<Integer> powersOfTwo = Stream.iterate(1, n -> n * 2)
- .takeWhile(n -> n < 100)
- .toList(); // [1, 2, 4, 8, 16, 32, 64]
// HANGS / INFINITE LOOP: filter does not know the sequence is monotonic!
- // Stream.iterate(1, n -> n * 2).filter(n -> n < 100).toList();
- 5. Non-Interference Rule and Common Traps
- The Non-Interference Rule
Stream operations must be non-interfering . This means that during the execution of a stream pipeline, the stream's data source must not be structurally modified (elements added, removed, or replaced). List<String> list = new ArrayList<>(List.of("alpha", "beta", "gamma"));
- // VIOLATION: Mutating the backing source inside a stream operation
- try {
- list.stream()
- .peek(s -> {
- if (s.equals("beta")) {
list.add("delta"); // Modifying backing collection!
- }
- })
- .forEach(System.out::println);
- } catch (ConcurrentModificationException e) {
- System.out.println("Caught ConcurrentModificationException due to interference!");
- }
- Stateful Operations on Infinite Streams Trap
Applying sorted() or distinct() to an unbounded infinite stream without a preceding limit() or takeWhile() causes an infinite loop or OutOfMemoryError , because the stateful operation must buffer all elements before emitting any.