Master the Stream API including object and primitive streams, stream creation, intermediate and terminal operations, filtering, transforming, processing, and sorting for the OCP 21 exam.
Table of Contents
1. Stream API Overview
The Stream API (introduced in Java 8) provides a functional approach to processing collections of data. Streams enable declarative, chainable operations on data sequences.
1.1 Key Concepts
- Stream: A sequence of elements supporting sequential and parallel aggregate operations
- Source: Collections, arrays, I/O channels, or generator functions
- Intermediate Operations: Return a stream, can be chained, lazy evaluation
- Terminal Operations: Produce a result or side effect, trigger stream processing
- Lazy Evaluation: Operations are not executed until a terminal operation is called
1.2 Stream vs Collection
| Aspect | Collection | Stream |
|---|---|---|
| Storage | Stores data | Does not store data |
| Operations | External iteration | Internal iteration |
| Evaluation | Eager | Lazy |
| Reusability | Can be reused | Single-use (consumed after terminal operation) |
2. Creating Streams
2.1 From Collections
List<String> list = Arrays.asList("a", "b", "c");
Stream<String> stream1 = list.stream();
Set<Integer> set = Set.of(1, 2, 3);
Stream<Integer> stream2 = set.stream();
Map<String, Integer> map = Map.of("a", 1, "b", 2);
Stream<String> keyStream = map.keySet().stream();
Stream<Integer> valueStream = map.values().stream();
Stream<Map.Entry<String, Integer>> entryStream = map.entrySet().stream();
2.2 From Arrays
String[] array = {"a", "b", "c"};
Stream<String> stream1 = Arrays.stream(array);
Stream<String> stream2 = Arrays.stream(array, 1, 3); // From index 1 to 3 (exclusive)
int[] intArray = {1, 2, 3, 4, 5};
IntStream intStream = Arrays.stream(intArray);
2.3 Static Factory Methods
// Stream.of() - from values
Stream<String> stream1 = Stream.of("a", "b", "c");
Stream<Integer> stream2 = Stream.of(1, 2, 3);
// Stream.empty() - empty stream
Stream<String> empty = Stream.empty();
// Stream.generate() - infinite stream
Stream<Double> random = Stream.generate(Math::random);
Stream<Integer> ones = Stream.generate(() -> 1);
// Stream.iterate() - infinite stream with seed and function
Stream<Integer> numbers = Stream.iterate(0, n -> n + 1);
Stream<Integer> evens = Stream.iterate(0, n -> n + 2);
// Stream.iterate() with predicate (Java 9+)
Stream<Integer> limited = Stream.iterate(0, n -> n < 10, n -> n + 1);
3. Intermediate Operations
Intermediate operations return a stream and are lazy. They are not executed until a terminal operation is invoked.
3.1 Filtering
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// filter() - keep elements matching predicate
List<Integer> evens = numbers.stream()
.filter(n -> n % 2 == 0)
.collect(Collectors.toList()); // [2, 4, 6, 8, 10]
// distinct() - remove duplicates
List<Integer> unique = Arrays.asList(1, 2, 2, 3, 3, 3).stream()
.distinct()
.collect(Collectors.toList()); // [1, 2, 3]
// limit() - limit number of elements
List<Integer> first5 = numbers.stream()
.limit(5)
.collect(Collectors.toList()); // [1, 2, 3, 4, 5]
// skip() - skip first n elements
List<Integer> skip5 = numbers.stream()
.skip(5)
.collect(Collectors.toList()); // [6, 7, 8, 9, 10]
3.2 Mapping
List<String> words = Arrays.asList("hello", "world", "java");
// map() - transform each element
List<String> upper = words.stream()
.map(String::toUpperCase)
.collect(Collectors.toList()); // [HELLO, WORLD, JAVA]
List<Integer> lengths = words.stream()
.map(String::length)
.collect(Collectors.toList()); // [5, 5, 4]
// flatMap() - flatten nested structures
List<List<Integer>> nested = Arrays.asList(
Arrays.asList(1, 2),
Arrays.asList(3, 4),
Arrays.asList(5, 6)
);
List<Integer> flat = nested.stream()
.flatMap(List::stream)
.collect(Collectors.toList()); // [1, 2, 3, 4, 5, 6]
3.3 Sorting
List<String> names = Arrays.asList("Charlie", "Alice", "Bob");
// sorted() - natural order (requires Comparable)
List<String> sorted = names.stream()
.sorted()
.collect(Collectors.toList()); // [Alice, Bob, Charlie]
// sorted(Comparator) - custom order
List<String> reverse = names.stream()
.sorted(Comparator.reverseOrder())
.collect(Collectors.toList()); // [Charlie, Bob, Alice]
List<String> byLength = names.stream()
.sorted(Comparator.comparing(String::length))
.collect(Collectors.toList()); // [Bob, Alice, Charlie]
4. Terminal Operations
Terminal operations produce a result or side effect and trigger the execution of the stream pipeline.
4.1 Collecting Results
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
// collect() - collect to collection
List<String> list = names.stream()
.filter(s -> s.length() > 3)
.collect(Collectors.toList());
Set<String> set = names.stream()
.collect(Collectors.toSet());
// collect() - to specific collection
LinkedList<String> linkedList = names.stream()
.collect(Collectors.toCollection(LinkedList::new));
4.2 Aggregation Operations
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
// count() - count elements
long count = numbers.stream().count(); // 5
// min() - find minimum
Optional<Integer> min = numbers.stream().min(Integer::compareTo);
// max() - find maximum
Optional<Integer> max = numbers.stream().max(Integer::compareTo);
// findFirst() - first element
Optional<Integer> first = numbers.stream().findFirst();
// findAny() - any element (useful for parallel streams)
Optional<Integer> any = numbers.stream().findAny();
// anyMatch() - any element matches predicate
boolean hasEven = numbers.stream().anyMatch(n -> n % 2 == 0); // true
// allMatch() - all elements match predicate
boolean allPositive = numbers.stream().allMatch(n -> n > 0); // true
// noneMatch() - no elements match predicate
boolean noNegative = numbers.stream().noneMatch(n -> n < 0); // true
4.3 Reduction Operations
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
// reduce() - with identity and accumulator
int sum1 = numbers.stream()
.reduce(0, (a, b) -> a + b); // 15
// reduce() - with accumulator only (returns Optional)
Optional<Integer> sum2 = numbers.stream()
.reduce((a, b) -> a + b); // Optional[15]
// reduce() - with identity, accumulator, and combiner (for parallel)
int sum3 = numbers.parallelStream()
.reduce(0, (a, b) -> a + b, (a, b) -> a + b); // 15
// Specialized reduction operations
int sum4 = numbers.stream().mapToInt(Integer::intValue).sum(); // 15
OptionalInt max = numbers.stream().mapToInt(Integer::intValue).max();
4.4 Iteration
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
// forEach() - iterate and perform side effect
names.stream()
.forEach(System.out::println);
// forEachOrdered() - maintain order (important for parallel streams)
names.parallelStream()
.forEachOrdered(System.out::println);
5. Primitive Streams
Java provides specialized stream types for primitives to avoid boxing overhead: IntStream, LongStream, and DoubleStream.
5.1 Creating Primitive Streams
// IntStream
IntStream intStream1 = IntStream.of(1, 2, 3, 4, 5);
IntStream intStream2 = IntStream.range(1, 6); // 1 to 5 (exclusive)
IntStream intStream3 = IntStream.rangeClosed(1, 5); // 1 to 5 (inclusive)
IntStream intStream4 = Arrays.stream(new int[]{1, 2, 3});
// LongStream
LongStream longStream = LongStream.of(1L, 2L, 3L);
LongStream range = LongStream.range(1, 10);
// DoubleStream
DoubleStream doubleStream = DoubleStream.of(1.0, 2.0, 3.0);
// Converting from object stream
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
IntStream intStream5 = numbers.stream().mapToInt(Integer::intValue);
5.2 Primitive Stream Operations
IntStream numbers = IntStream.of(1, 2, 3, 4, 5);
// Specialized terminal operations
int sum = numbers.sum(); // 15
OptionalDouble avg = numbers.average(); // OptionalDouble[3.0]
OptionalInt min = numbers.min(); // OptionalInt[1]
OptionalInt max = numbers.max(); // OptionalInt[5]
// Converting back to object stream
Stream<Integer> boxed = numbers.boxed();
// Converting to other primitive streams
LongStream longStream = numbers.asLongStream();
DoubleStream doubleStream = numbers.asDoubleStream();
6. Stream Characteristics
6.1 Stream Properties
- Lazy: Intermediate operations are not executed until terminal operation
- Single-use: Streams can only be consumed once
- Non-interfering: Source should not be modified during stream operations
- Stateless: Operations should be stateless (no dependency on mutable state)
6.2 Common Pitfalls
Stream<String> stream = Stream.of("a", "b", "c");
// WRONG: Reusing stream
stream.forEach(System.out::println);
stream.count(); // IllegalStateException - stream already consumed
// CORRECT: Create new stream for each operation
Stream.of("a", "b", "c").forEach(System.out::println);
Stream.of("a", "b", "c").count();
// WRONG: Modifying source during stream operation
List<String> list = new ArrayList<>(Arrays.asList("a", "b", "c"));
list.stream()
.filter(s -> {
list.remove(s); // ConcurrentModificationException
return true;
})
.collect(Collectors.toList());
7. Exam Key Points
Critical Concepts for OCP 21 Exam:
- Stream Creation:
collection.stream(),Arrays.stream(),Stream.of() - Intermediate Operations: filter, map, flatMap, distinct, sorted, limit, skip
- Terminal Operations: collect, forEach, reduce, count, min, max, findFirst, findAny
- Lazy Evaluation: Intermediate operations don't execute until terminal operation
- Single-use: Streams can only be consumed once
- Primitive Streams: IntStream, LongStream, DoubleStream for performance
- map() vs flatMap(): map transforms, flatMap flattens nested structures
- Optional Return: min(), max(), findFirst() return Optional
- Predicate Operations: anyMatch(), allMatch(), noneMatch() return boolean
- Reduction: reduce() with identity, accumulator, and combiner
- Collectors: toList(), toSet(), toCollection() for collecting results
- Method References: Can be used in stream operations
0 Comments