Stream API Introduction in Java: Complete Guide

Table of Contents

  1. Introduction to Stream API
  2. Stream Creation
  3. Intermediate Operations
  4. Terminal Operations
  5. Collectors
  6. Parallel Streams
  7. Advanced Stream Operations
  8. Best Practices

Introduction to Stream API

The Stream API, introduced in Java 8, provides a functional approach to process sequences of elements. It enables declarative processing of collections and supports parallel execution.

Key Characteristics

  • Not a data structure - doesn't store elements
  • Functional in nature - doesn't modify source data
  • Lazy evaluation - operations are performed only when needed
  • Parallelizable - can process elements in parallel

Stream vs Collection

import java.util.*;
import java.util.stream.*;
public class StreamVsCollection {
public static void main(String[] args) {
List<String> names = Arrays.asList("John", "Jane", "Jack", "Doe");
// Traditional approach (imperative)
System.out.println("=== Traditional Approach ===");
List<String> filteredNames = new ArrayList<>();
for (String name : names) {
if (name.startsWith("J")) {
filteredNames.add(name.toUpperCase());
}
}
System.out.println(filteredNames);
// Stream approach (declarative)
System.out.println("\n=== Stream Approach ===");
List<String> streamResult = names.stream()
.filter(name -> name.startsWith("J"))
.map(String::toUpperCase)
.collect(Collectors.toList());
System.out.println(streamResult);
}
}

Stream Creation

Different Ways to Create Streams

import java.util.*;
import java.util.stream.*;
import java.nio.file.*;
import java.io.IOException;
public class StreamCreation {
public static void main(String[] args) {
System.out.println("=== Different Ways to Create Streams ===");
// 1. From Collection
List<String> list = Arrays.asList("A", "B", "C");
Stream<String> listStream = list.stream();
System.out.println("From List: " + listStream.count() + " elements");
// 2. From Array
String[] array = {"X", "Y", "Z"};
Stream<String> arrayStream = Arrays.stream(array);
System.out.println("From Array: " + arrayStream.count() + " elements");
// 3. Using Stream.of()
Stream<String> directStream = Stream.of("Apple", "Banana", "Cherry");
System.out.println("Direct stream: " + directStream.count() + " elements");
// 4. Using Stream.builder()
Stream.Builder<String> builder = Stream.builder();
builder.add("First");
builder.add("Second");
builder.add("Third");
Stream<String> builtStream = builder.build();
System.out.println("Built stream: " + builtStream.count() + " elements");
// 5. Using Stream.generate()
Stream<String> generatedStream = Stream.generate(() -> "Hello")
.limit(3);
System.out.println("Generated stream: " + generatedStream.count() + " elements");
// 6. Using Stream.iterate()
Stream<Integer> iteratedStream = Stream.iterate(1, n -> n * 2)
.limit(5);
System.out.println("Iterated stream: " + 
iteratedStream.collect(Collectors.toList()));
// 7. From File
try {
Stream<String> fileStream = Files.lines(Paths.get("example.txt"));
System.out.println("File stream created");
fileStream.close();
} catch (IOException e) {
System.out.println("File not found - this is expected in demo");
}
// 8. Primitive Streams
IntStream intStream = IntStream.range(1, 6);
LongStream longStream = LongStream.rangeClosed(1, 5);
DoubleStream doubleStream = DoubleStream.of(1.1, 2.2, 3.3);
System.out.println("IntStream: " + intStream.sum());
System.out.println("DoubleStream: " + doubleStream.sum());
}
}

Empty and Concatenated Streams

import java.util.stream.*;
public class SpecialStreams {
public static void main(String[] args) {
// Empty stream
Stream<String> emptyStream = Stream.empty();
System.out.println("Empty stream count: " + emptyStream.count());
// Concatenating streams
Stream<String> stream1 = Stream.of("A", "B", "C");
Stream<String> stream2 = Stream.of("X", "Y", "Z");
Stream<String> concatenated = Stream.concat(stream1, stream2);
System.out.println("Concatenated stream: " + 
concatenated.collect(Collectors.toList()));
// Stream from Optional
Optional<String> optional = Optional.of("Hello");
Stream<String> optionalStream = optional.stream();
System.out.println("Optional stream: " + 
optionalStream.collect(Collectors.toList()));
Optional<String> emptyOptional = Optional.empty();
Stream<String> emptyOptionalStream = emptyOptional.stream();
System.out.println("Empty optional stream count: " + 
emptyOptionalStream.count());
}
}

Intermediate Operations

Filtering Operations

import java.util.*;
import java.util.stream.*;
public class FilteringOperations {
public static void main(String[] args) {
List<String> names = Arrays.asList("John", "Jane", "Jack", "Doe", "Sarah", "Mike");
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
System.out.println("Original names: " + names);
System.out.println("Original numbers: " + numbers);
// filter() - elements matching predicate
List<String> jNames = names.stream()
.filter(name -> name.startsWith("J"))
.collect(Collectors.toList());
System.out.println("\nNames starting with 'J': " + jNames);
// distinct() - remove duplicates
List<String> withDuplicates = Arrays.asList("A", "B", "A", "C", "B", "D");
List<String> distinct = withDuplicates.stream()
.distinct()
.collect(Collectors.toList());
System.out.println("Distinct elements: " + distinct);
// limit() - first n elements
List<String> firstThree = names.stream()
.limit(3)
.collect(Collectors.toList());
System.out.println("First 3 names: " + firstThree);
// skip() - skip first n elements
List<String> skipFirstTwo = names.stream()
.skip(2)
.collect(Collectors.toList());
System.out.println("Skip first 2: " + skipFirstTwo);
// takeWhile() - take elements while predicate is true (Java 9+)
List<Integer> takeWhile = numbers.stream()
.takeWhile(n -> n < 5)  // Take while number is less than 5
.collect(Collectors.toList());
System.out.println("Take while < 5: " + takeWhile);
// dropWhile() - drop elements while predicate is true (Java 9+)
List<Integer> dropWhile = numbers.stream()
.dropWhile(n -> n < 5)  // Drop while number is less than 5
.collect(Collectors.toList());
System.out.println("Drop while < 5: " + dropWhile);
}
}

Mapping Operations

import java.util.*;
import java.util.stream.*;
class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() { return name; }
public int getAge() { return age; }
@Override
public String toString() {
return name + "(" + age + ")";
}
}
public class MappingOperations {
public static void main(String[] args) {
List<Person> people = Arrays.asList(
new Person("John", 25),
new Person("Jane", 30),
new Person("Jack", 35),
new Person("Doe", 28)
);
List<String> names = Arrays.asList("apple", "banana", "cherry");
List<List<String>> nestedLists = Arrays.asList(
Arrays.asList("A", "B"),
Arrays.asList("C", "D", "E"),
Arrays.asList("F")
);
System.out.println("Original people: " + people);
// map() - transform each element
List<String> personNames = people.stream()
.map(Person::getName)
.collect(Collectors.toList());
System.out.println("\nPerson names: " + personNames);
List<Integer> nameLengths = names.stream()
.map(String::length)
.collect(Collectors.toList());
System.out.println("Name lengths: " + nameLengths);
// flatMap() - flatten nested structures
List<String> flattened = nestedLists.stream()
.flatMap(List::stream)
.collect(Collectors.toList());
System.out.println("Flattened list: " + flattened);
// mapToInt(), mapToLong(), mapToDouble() - primitive specialization
IntStream ages = people.stream()
.mapToInt(Person::getAge);
System.out.println("Ages: " + ages.sum());
// peek() - perform action without modifying (debugging)
List<String> processedNames = names.stream()
.peek(name -> System.out.println("Processing: " + name))
.map(String::toUpperCase)
.peek(name -> System.out.println("Converted to: " + name))
.collect(Collectors.toList());
System.out.println("Final result: " + processedNames);
}
}

Sorting Operations

import java.util.*;
import java.util.stream.*;
public class SortingOperations {
public static void main(String[] args) {
List<String> names = Arrays.asList("John", "Jane", "Jack", "Alice", "Bob");
List<Integer> numbers = Arrays.asList(5, 2, 8, 1, 9, 3);
List<Person> people = Arrays.asList(
new Person("John", 25),
new Person("Jane", 30),
new Person("Jack", 22),
new Person("Alice", 30)
);
// Natural order sorting
List<String> sortedNames = names.stream()
.sorted()
.collect(Collectors.toList());
System.out.println("Sorted names: " + sortedNames);
// Reverse order sorting
List<String> reverseSorted = names.stream()
.sorted(Comparator.reverseOrder())
.collect(Collectors.toList());
System.out.println("Reverse sorted: " + reverseSorted);
// Custom comparator
List<String> lengthSorted = names.stream()
.sorted(Comparator.comparing(String::length))
.collect(Collectors.toList());
System.out.println("Sorted by length: " + lengthSorted);
// Multiple criteria sorting
List<Person> sortedPeople = people.stream()
.sorted(Comparator.comparing(Person::getAge)
.thenComparing(Person::getName))
.collect(Collectors.toList());
System.out.println("People sorted by age then name: " + sortedPeople);
// Complex sorting
List<Person> complexSorted = people.stream()
.sorted(Comparator.comparing(Person::getAge).reversed()
.thenComparing(Person::getName))
.collect(Collectors.toList());
System.out.println("People sorted by age desc then name: " + complexSorted);
}
}

Terminal Operations

Short-circuiting Terminal Operations

import java.util.*;
import java.util.stream.*;
public class ShortCircuitingOperations {
public static void main(String[] args) {
List<String> names = Arrays.asList("John", "Jane", "Jack", "Doe", "Sarah");
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
System.out.println("Original: " + names);
// anyMatch() - true if any element matches predicate
boolean hasJ = names.stream()
.anyMatch(name -> name.startsWith("J"));
System.out.println("\nAny name starts with 'J': " + hasJ);
// allMatch() - true if all elements match predicate
boolean allHaveJ = names.stream()
.allMatch(name -> name.contains("J"));
System.out.println("All names contain 'J': " + allHaveJ);
// noneMatch() - true if no elements match predicate
boolean noneHaveZ = names.stream()
.noneMatch(name -> name.contains("Z"));
System.out.println("No names contain 'Z': " + noneHaveZ);
// findFirst() - first element in stream
Optional<String> first = names.stream()
.filter(name -> name.startsWith("J"))
.findFirst();
System.out.println("First name starting with 'J': " + first.orElse("None"));
// findAny() - any element (useful in parallel streams)
Optional<String> any = names.stream()
.filter(name -> name.length() > 3)
.findAny();
System.out.println("Any name with length > 3: " + any.orElse("None"));
// Demonstrating short-circuiting behavior
Optional<Integer> firstEven = numbers.stream()
.filter(n -> {
System.out.println("Checking: " + n);
return n % 2 == 0;
})
.findFirst();
System.out.println("First even (short-circuiting): " + firstEven.get());
}
}

Reduction Operations

import java.util.*;
import java.util.stream.*;
public class ReductionOperations {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
List<String> words = Arrays.asList("Hello", "World", "Stream", "API");
System.out.println("Numbers: " + numbers);
System.out.println("Words: " + words);
// count() - number of elements
long count = numbers.stream().count();
System.out.println("\nCount: " + count);
// min() and max()
Optional<Integer> min = numbers.stream().min(Integer::compare);
Optional<Integer> max = numbers.stream().max(Integer::compare);
System.out.println("Min: " + min.orElse(-1));
System.out.println("Max: " + max.orElse(-1));
// reduce() - custom reduction
Optional<Integer> sum = numbers.stream()
.reduce(Integer::sum);
System.out.println("Sum: " + sum.orElse(0));
Integer product = numbers.stream()
.reduce(1, (a, b) -> a * b);
System.out.println("Product: " + product);
// Complex reduction
String concatenated = words.stream()
.reduce("", (a, b) -> a + " " + b);
System.out.println("Concatenated: " + concatenated.trim());
// Statistical operations
IntSummaryStatistics stats = numbers.stream()
.mapToInt(Integer::intValue)
.summaryStatistics();
System.out.println("\nStatistics:");
System.out.println("  Count: " + stats.getCount());
System.out.println("  Sum: " + stats.getSum());
System.out.println("  Min: " + stats.getMin());
System.out.println("  Max: " + stats.getMax());
System.out.println("  Average: " + stats.getAverage());
// forEach() - perform action on each element
System.out.println("\nPrinting elements:");
numbers.stream()
.forEach(n -> System.out.print(n + " "));
System.out.println();
// forEachOrdered() - maintain order in parallel streams
System.out.println("Printing in order:");
numbers.parallelStream()
.forEachOrdered(n -> System.out.print(n + " "));
System.out.println();
// toArray() - convert to array
Integer[] numberArray = numbers.stream().toArray(Integer[]::new);
System.out.println("Array: " + Arrays.toString(numberArray));
}
}

Collectors

Built-in Collectors

import java.util.*;
import java.util.stream.*;
import static java.util.stream.Collectors.*;
public class BuiltInCollectors {
public static void main(String[] args) {
List<Person> people = Arrays.asList(
new Person("John", 25),
new Person("Jane", 30),
new Person("Jack", 35),
new Person("Alice", 25),
new Person("Bob", 30)
);
List<String> names = Arrays.asList("John", "Jane", "Jack", "Alice", "Bob");
System.out.println("People: " + people);
// toList()
List<String> nameList = people.stream()
.map(Person::getName)
.collect(toList());
System.out.println("\nNames list: " + nameList);
// toSet()
Set<Integer> ageSet = people.stream()
.map(Person::getAge)
.collect(toSet());
System.out.println("Age set: " + ageSet);
// toCollection() - specific collection type
LinkedList<String> linkedNames = people.stream()
.map(Person::getName)
.collect(toCollection(LinkedList::new));
System.out.println("Linked list: " + linkedNames);
// toMap()
Map<String, Integer> nameToAge = people.stream()
.collect(toMap(Person::getName, Person::getAge));
System.out.println("Name to age map: " + nameToAge);
// groupingBy()
Map<Integer, List<Person>> peopleByAge = people.stream()
.collect(groupingBy(Person::getAge));
System.out.println("Grouped by age: " + peopleByAge);
// partitioningBy()
Map<Boolean, List<Person>> partitioned = people.stream()
.collect(partitioningBy(p -> p.getAge() > 28));
System.out.println("Partitioned by age > 28: " + partitioned);
// joining()
String joinedNames = names.stream()
.collect(joining(", ", "[", "]"));
System.out.println("Joined names: " + joinedNames);
// counting()
Long count = people.stream()
.collect(counting());
System.out.println("Count: " + count);
// summingInt(), averagingInt(), summarizingInt()
Integer totalAge = people.stream()
.collect(summingInt(Person::getAge));
System.out.println("Total age: " + totalAge);
Double averageAge = people.stream()
.collect(averagingInt(Person::getAge));
System.out.println("Average age: " + averageAge);
IntSummaryStatistics ageStats = people.stream()
.collect(summarizingInt(Person::getAge));
System.out.println("Age statistics: " + ageStats);
// maxBy(), minBy()
Optional<Person> oldest = people.stream()
.collect(maxBy(Comparator.comparing(Person::getAge)));
System.out.println("Oldest: " + oldest.orElse(null));
// mapping() - map before collecting
Set<String> upperCaseNames = people.stream()
.collect(mapping(Person::getName, toSet()))
.stream()
.map(String::toUpperCase)
.collect(toSet());
System.out.println("Upper case names: " + upperCaseNames);
// collectingAndThen() - perform final transformation
String unmodifiableNames = people.stream()
.map(Person::getName)
.collect(collectingAndThen(toList(), 
list -> Collections.unmodifiableList(list).toString()));
System.out.println("Unmodifiable list string: " + unmodifiableNames);
}
}

Custom Collectors

import java.util.*;
import java.util.function.*;
import java.util.stream.*;
class CustomCollectors {
// Custom collector to join strings with prefix and suffix
public static Collector<String, ?, String> joiningWithFormat(
String delimiter, String prefix, String suffix) {
return Collector.of(
() -> new StringJoiner(delimiter, prefix, suffix),
StringJoiner::add,
StringJoiner::merge,
StringJoiner::toString
);
}
// Custom collector to calculate average of integers
public static Collector<Integer, ?, Double> averagingInt() {
return Collector.of(
() -> new long[2],  // [sum, count]
(acc, value) -> {
acc[0] += value;
acc[1]++;
},
(acc1, acc2) -> {
acc1[0] += acc2[0];
acc1[1] += acc2[1];
return acc1;
},
acc -> acc[1] == 0 ? 0.0 : (double) acc[0] / acc[1]
);
}
// Custom collector to find most frequent element
public static <T> Collector<T, ?, Optional<T>> mostFrequent() {
return Collector.of(
HashMap<T, Integer>::new,
(map, element) -> map.merge(element, 1, Integer::sum),
(map1, map2) -> {
map2.forEach((key, value) -> 
map1.merge(key, value, Integer::sum));
return map1;
},
map -> map.entrySet().stream()
.max(Map.Entry.comparingByValue())
.map(Map.Entry::getKey)
);
}
}
public class CustomCollectorsDemo {
public static void main(String[] args) {
List<String> names = Arrays.asList("John", "Jane", "John", "Alice", "Bob", "Alice", "Alice");
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
// Using custom joining collector
String formatted = names.stream()
.collect(CustomCollectors.joiningWithFormat(" | ", ">>> ", " <<<"));
System.out.println("Formatted join: " + formatted);
// Using custom average collector
Double average = numbers.stream()
.collect(CustomCollectors.averagingInt());
System.out.println("Custom average: " + average);
// Using most frequent collector
Optional<String> mostFrequent = names.stream()
.collect(CustomCollectors.mostFrequent());
System.out.println("Most frequent: " + mostFrequent.orElse("None"));
// Complex custom collector: group by and transform
Collector<Person, ?, Map<Integer, String>> ageToNames = 
Collector.of(
HashMap::new,
(map, person) -> 
map.merge(person.getAge(), person.getName(), 
(oldVal, newVal) -> oldVal + ", " + newVal),
(map1, map2) -> {
map2.forEach((age, name) -> 
map1.merge(age, name, (oldVal, newVal) -> oldVal + ", " + newVal));
return map1;
}
);
List<Person> people = Arrays.asList(
new Person("John", 25),
new Person("Jane", 30),
new Person("Jack", 25),
new Person("Alice", 30)
);
Map<Integer, String> ageToNameMap = people.stream()
.collect(ageToNames);
System.out.println("Age to names: " + ageToNameMap);
}
}

Parallel Streams

Parallel Stream Operations

import java.util.*;
import java.util.stream.*;
import java.util.concurrent.*;
public class ParallelStreams {
public static void main(String[] args) {
List<Integer> numbers = IntStream.range(1, 101)
.boxed()
.collect(Collectors.toList());
System.out.println("=== Sequential vs Parallel Streams ===");
// Sequential processing
long sequentialStart = System.currentTimeMillis();
List<Integer> sequentialSquares = numbers.stream()
.map(n -> {
try { Thread.sleep(10); } catch (InterruptedException e) {} // Simulate work
return n * n;
})
.collect(Collectors.toList());
long sequentialTime = System.currentTimeMillis() - sequentialStart;
// Parallel processing
long parallelStart = System.currentTimeMillis();
List<Integer> parallelSquares = numbers.parallelStream()
.map(n -> {
try { Thread.sleep(10); } catch (InterruptedException e) {} // Simulate work
return n * n;
})
.collect(Collectors.toList());
long parallelTime = System.currentTimeMillis() - parallelStart;
System.out.println("Sequential time: " + sequentialTime + "ms");
System.out.println("Parallel time: " + parallelTime + "ms");
System.out.println("Speedup: " + (double) sequentialTime / parallelTime + "x");
// Ordering in parallel streams
System.out.println("\n=== Ordering Considerations ===");
List<Integer> unordered = numbers.parallelStream()
.map(n -> n * 2)
.collect(Collectors.toList());
System.out.println("Parallel result size: " + unordered.size());
// forEach vs forEachOrdered
System.out.println("Parallel forEach (unordered):");
numbers.parallelStream()
.limit(5)
.forEach(n -> System.out.print(n + " "));
System.out.println();
System.out.println("Parallel forEachOrdered:");
numbers.parallelStream()
.limit(5)
.forEachOrdered(n -> System.out.print(n + " "));
System.out.println();
// When to use parallel streams
demonstrateParallelUseCases();
}
public static void demonstrateParallelUseCases() {
System.out.println("\n=== Appropriate Use Cases for Parallel Streams ===");
// Good: CPU-intensive operations
long sum = LongStream.range(1, 10_000_000)
.parallel()
.filter(n -> n % 2 == 0)
.sum();
System.out.println("Sum of even numbers: " + sum);
// Good: Independent operations
List<String> words = Arrays.asList("apple", "banana", "cherry", "date", "elderberry");
Map<Integer, Long> lengthCount = words.parallelStream()
.collect(Collectors.groupingByConcurrent(
String::length,
Collectors.counting()
));
System.out.println("Word length counts: " + lengthCount);
// Bad: Stateful operations (can cause issues)
List<Integer> problematic = Collections.synchronizedList(new ArrayList<>());
IntStream.range(1, 1000)
.parallel()
.filter(n -> n % 2 == 0)
.forEach(problematic::add);  // Stateful - can cause contention
System.out.println("Problematic result size: " + problematic.size());
}
}

Parallel Stream Considerations

import java.util.*;
import java.util.concurrent.*;
import java.util.stream.*;
public class ParallelStreamConsiderations {
public static void demonstrateConsiderations() {
System.out.println("=== Parallel Stream Considerations ===");
// 1. Common ForkJoinPool usage
System.out.println("\n1. Common ForkJoinPool:");
long commonPoolParallelism = ForkJoinPool.commonPool().getParallelism();
System.out.println("Common pool parallelism: " + commonPoolParallelism);
// 2. Custom ForkJoinPool
System.out.println("\n2. Custom ForkJoinPool:");
ForkJoinPool customPool = new ForkJoinPool(4);
try {
customPool.submit(() -> {
IntStream.range(1, 100)
.parallel()
.forEach(n -> System.out.println(Thread.currentThread().getName()));
}).get();
} catch (Exception e) {
e.printStackTrace();
}
// 3. Ordering issues
System.out.println("\n3. Ordering in parallel streams:");
List<Integer> numbers = IntStream.range(1, 11).boxed().collect(Collectors.toList());
System.out.println("Sequential order:");
numbers.stream()
.map(n -> n * 2)
.forEach(n -> System.out.print(n + " "));
System.out.println();
System.out.println("Parallel order (may vary):");
numbers.parallelStream()
.map(n -> n * 2)
.forEach(n -> System.out.print(n + " "));
System.out.println();
// 4. Stateful operations problem
System.out.println("\n4. Stateful operations issue:");
List<Integer> statefulResult = new ArrayList<>();
IntStream.range(1, 1000)
.parallel()
.filter(n -> n % 2 == 0)
.forEach(statefulResult::add);  // Unsafe!
System.out.println("Stateful result size (may be incorrect): " + statefulResult.size());
// 5. Safe alternative
List<Integer> safeResult = IntStream.range(1, 1000)
.parallel()
.filter(n -> n % 2 == 0)
.boxed()
.collect(Collectors.toList());  // Safe collection
System.out.println("Safe result size: " + safeResult.size());
// 6. When NOT to use parallel streams
System.out.println("\n6. When parallel streams may hurt performance:");
// Small datasets
long smallSeqTime = measureTime(() -> 
IntStream.range(1, 1000).sum());
long smallParTime = measureTime(() -> 
IntStream.range(1, 1000).parallel().sum());
System.out.println("Small dataset - Sequential: " + smallSeqTime + "ns");
System.out.println("Small dataset - Parallel: " + smallParTime + "ns");
System.out.println("Parallel overhead evident for small datasets");
}
private static long measureTime(Runnable operation) {
long start = System.nanoTime();
operation.run();
return System.nanoTime() - start;
}
public static void main(String[] args) {
demonstrateConsiderations();
}
}

Advanced Stream Operations

Advanced Stream Patterns

import java.util.*;
import java.util.stream.*;
import java.util.function.*;
public class AdvancedStreamPatterns {
public static void demonstratePatterns() {
System.out.println("=== Advanced Stream Patterns ===");
List<Person> people = Arrays.asList(
new Person("John", 25),
new Person("Jane", 30),
new Person("Jack", 35),
new Person("Alice", 25),
new Person("Bob", 30),
new Person("Charlie", 40)
);
// Pattern 1: Chaining Optional with Stream
System.out.println("\n1. Optional with Stream:");
Optional<String> firstLongName = people.stream()
.map(Person::getName)
.filter(name -> name.length() > 4)
.findFirst();
firstLongName.ifPresent(name -> 
System.out.println("First long name: " + name));
// Pattern 2: Stream of Optionals
System.out.println("\n2. Stream of Optionals:");
List<Optional<String>> optionalNames = Arrays.asList(
Optional.of("John"),
Optional.empty(),
Optional.of("Jane"),
Optional.empty(),
Optional.of("Alice")
);
List<String> presentNames = optionalNames.stream()
.flatMap(Optional::stream)
.collect(Collectors.toList());
System.out.println("Present names: " + presentNames);
// Pattern 3: Infinite streams with limit
System.out.println("\n3. Infinite streams:");
List<Double> randomNumbers = Stream.generate(Math::random)
.limit(5)
.collect(Collectors.toList());
System.out.println("Random numbers: " + randomNumbers);
// Pattern 4: Stream.iterate for sequences
System.out.println("\n4. Stream.iterate for sequences:");
List<Integer> fibonacci = Stream.iterate(new int[]{0, 1}, fib -> new int[]{fib[1], fib[0] + fib[1]})
.limit(10)
.map(fib -> fib[0])
.collect(Collectors.toList());
System.out.println("Fibonacci: " + fibonacci);
// Pattern 5: Complex grouping
System.out.println("\n5. Complex grouping:");
Map<String, List<Person>> groupedByAgeRange = people.stream()
.collect(Collectors.groupingBy(
person -> {
int age = person.getAge();
if (age < 30) return "Young";
else if (age < 40) return "Middle";
else return "Senior";
}
));
System.out.println("Grouped by age range: " + groupedByAgeRange);
// Pattern 6: Teeing collector (Java 12+)
System.out.println("\n6. Teeing collector (conceptual):");
// Collectors.teeing() allows two collectors to work simultaneously
// Pattern 7: Custom reduction with complex logic
System.out.println("\n7. Custom complex reduction:");
String analysis = people.stream()
.collect(Collectors.teeing(
Collectors.averagingInt(Person::getAge),
Collectors.mapping(Person::getName, Collectors.joining(", ")),
(avgAge, names) -> String.format("Average age: %.1f, Names: %s", avgAge, names)
));
System.out.println("Analysis: " + analysis);
}
// Pattern: Stream pipeline with exception handling
public static List<Integer> parseNumbersSafely(List<String> numberStrings) {
return numberStrings.stream()
.flatMap(str -> {
try {
return Stream.of(Integer.parseInt(str));
} catch (NumberFormatException e) {
System.err.println("Invalid number: " + str);
return Stream.empty();
}
})
.collect(Collectors.toList());
}
// Pattern: Lazy evaluation demonstration
public static void demonstrateLazyEvaluation() {
System.out.println("\n=== Lazy Evaluation ===");
List<String> names = Arrays.asList("John", "Jane", "Jack", "Doe");
System.out.println("Without terminal operation (nothing happens):");
Stream<String> intermediate = names.stream()
.filter(name -> {
System.out.println("Filtering: " + name);
return name.startsWith("J");
})
.map(name -> {
System.out.println("Mapping: " + name);
return name.toUpperCase();
});
System.out.println("Intermediate operations defined but not executed");
System.out.println("\nWith terminal operation (execution happens):");
List<String> result = intermediate.collect(Collectors.toList());
System.out.println("Result: " + result);
}
public static void main(String[] args) {
demonstratePatterns();
demonstrateLazyEvaluation();
// Test safe number parsing
List<String> mixedInput = Arrays.asList("1", "2", "abc", "4", "5xyz");
List<Integer> parsedNumbers = parseNumbersSafely(mixedInput);
System.out.println("\nSafe parsing result: " + parsedNumbers);
}
}

Real-World Stream Examples

import java.util.*;
import java.util.stream.*;
import java.time.*;
import java.time.temporal.ChronoUnit;
class Transaction {
private String id;
private double amount;
private LocalDateTime timestamp;
private String type; // "DEBIT" or "CREDIT"
public Transaction(String id, double amount, LocalDateTime timestamp, String type) {
this.id = id;
this.amount = amount;
this.timestamp = timestamp;
this.type = type;
}
// Getters
public String getId() { return id; }
public double getAmount() { return amount; }
public LocalDateTime getTimestamp() { return timestamp; }
public String getType() { return type; }
@Override
public String toString() {
return String.format("Transaction{id='%s', amount=%.2f, type='%s'}", 
id, amount, type);
}
}
public class RealWorldStreamExamples {
public static void financialAnalysis() {
System.out.println("=== Financial Transaction Analysis ===");
List<Transaction> transactions = Arrays.asList(
new Transaction("T1", 100.0, LocalDateTime.now().minusDays(1), "CREDIT"),
new Transaction("T2", 50.0, LocalDateTime.now().minusHours(6), "DEBIT"),
new Transaction("T3", 200.0, LocalDateTime.now().minusHours(2), "CREDIT"),
new Transaction("T4", 75.0, LocalDateTime.now().minusMinutes(30), "DEBIT"),
new Transaction("T5", 150.0, LocalDateTime.now().minusMinutes(10), "CREDIT")
);
// Total credit amount
double totalCredit = transactions.stream()
.filter(t -> "CREDIT".equals(t.getType()))
.mapToDouble(Transaction::getAmount)
.sum();
System.out.println("Total credit: $" + totalCredit);
// Total debit amount
double totalDebit = transactions.stream()
.filter(t -> "DEBIT".equals(t.getType()))
.mapToDouble(Transaction::getAmount)
.sum();
System.out.println("Total debit: $" + totalDebit);
// Net balance
double netBalance = totalCredit - totalDebit;
System.out.println("Net balance: $" + netBalance);
// Recent transactions (last 3 hours)
List<Transaction> recent = transactions.stream()
.filter(t -> t.getTimestamp().isAfter(LocalDateTime.now().minusHours(3)))
.collect(Collectors.toList());
System.out.println("Recent transactions: " + recent);
// Transaction statistics
DoubleSummaryStatistics stats = transactions.stream()
.mapToDouble(Transaction::getAmount)
.summaryStatistics();
System.out.println("Transaction stats: " + stats);
}
public static void employeeManagement() {
System.out.println("\n=== Employee Management System ===");
class Employee {
String name;
String department;
double salary;
int experience;
Employee(String name, String department, double salary, int experience) {
this.name = name;
this.department = department;
this.salary = salary;
this.experience = experience;
}
// Getters
String getName() { return name; }
String getDepartment() { return department; }
double getSalary() { return salary; }
int getExperience() { return experience; }
}
List<Employee> employees = Arrays.asList(
new Employee("John", "Engineering", 75000, 3),
new Employee("Jane", "Engineering", 85000, 5),
new Employee("Bob", "Marketing", 60000, 2),
new Employee("Alice", "Marketing", 65000, 4),
new Employee("Charlie", "HR", 55000, 1),
new Employee("Diana", "HR", 60000, 3)
);
// Department-wise average salary
Map<String, Double> avgSalaryByDept = employees.stream()
.collect(Collectors.groupingBy(
Employee::getDepartment,
Collectors.averagingDouble(Employee::getSalary)
));
System.out.println("Average salary by department: " + avgSalaryByDept);
// Top 2 earners in each department
Map<String, List<Employee>> topEarnersByDept = employees.stream()
.collect(Collectors.groupingBy(
Employee::getDepartment,
Collectors.collectingAndThen(
Collectors.toList(),
list -> list.stream()
.sorted(Comparator.comparing(Employee::getSalary).reversed())
.limit(2)
.collect(Collectors.toList())
)
));
System.out.println("Top earners by department: " + topEarnersByDept);
// Employees eligible for promotion (experience > 3 years)
List<Employee> promotionEligible = employees.stream()
.filter(e -> e.getExperience() > 3)
.sorted(Comparator.comparing(Employee::getExperience).reversed())
.collect(Collectors.toList());
System.out.println("Promotion eligible: " + 
promotionEligible.stream().map(Employee::getName).collect(Collectors.toList()));
// Salary budget by department
Map<String, Double> budgetByDept = employees.stream()
.collect(Collectors.groupingBy(
Employee::getDepartment,
Collectors.summingDouble(Employee::getSalary)
));
System.out.println("Budget by department: " + budgetByDept);
}
public static void dataProcessingPipeline() {
System.out.println("\n=== Data Processing Pipeline ===");
List<String> rawData = Arrays.asList(
"user1,25,engineer",
"user2,30,manager",
"user3,22,intern",
"user4,35,engineer",
"user5,28,designer",
"INVALID_DATA",
"user6,40,manager"
);
// Complete data processing pipeline
Map<String, DoubleSummaryStatistics> roleStats = rawData.stream()
.filter(line -> line.contains(","))  // Filter valid lines
.map(line -> line.split(","))        // Split into parts
.filter(parts -> parts.length == 3)  // Ensure correct format
.map(parts -> new Object() {         // Create anonymous objects
String username = parts[0];
int age = Integer.parseInt(parts[1]);
String role = parts[2];
})
.filter(user -> user.age >= 18 && user.age <= 65)  // Validate age
.collect(Collectors.groupingBy(
user -> user.role,
Collectors.summarizingDouble(user -> user.age)
));
System.out.println("Role statistics:");
roleStats.forEach((role, stats) -> 
System.out.printf("  %s: count=%d, avg=%.1f, min=%d, max=%d%n",
role, stats.getCount(), stats.getAverage(), (int)stats.getMin(), (int)stats.getMax()));
}
public static void main(String[] args) {
financialAnalysis();
employeeManagement();
dataProcessingPipeline();
}
}

Best Practices

Stream API Best Practices

import java.util.*;
import java.util.stream.*;
import java.util.function.*;
public class StreamBestPractices {
public static void demonstrateBestPractices() {
System.out.println("=== Stream API Best Practices ===");
List<Person> people = Arrays.asList(
new Person("John", 25),
new Person("Jane", 30),
new Person("Jack", 35),
new Person("Alice", 25),
new Person("Bob", 30)
);
// 1. Prefer method references
System.out.println("\n1. Method References:");
List<String> namesWithRef = people.stream()
.map(Person::getName)  // Good: method reference
.collect(Collectors.toList());
List<String> namesWithLambda = people.stream()
.map(person -> person.getName())  // Avoid: unnecessary lambda
.collect(Collectors.toList());
System.out.println("Names: " + namesWithRef);
// 2. Avoid stateful lambda expressions
System.out.println("\n2. Stateless Operations:");
// Bad: stateful operation
AtomicInteger counter = new AtomicInteger();
List<String> badResult = people.stream()
.map(p -> p.getName() + counter.getAndIncrement())  // Stateful!
.collect(Collectors.toList());
System.out.println("Stateful result: " + badResult);
// Good: stateless operation
List<String> goodResult = IntStream.range(0, people.size())
.mapToObj(i -> people.get(i).getName() + i)
.collect(Collectors.toList());
System.out.println("Stateless result: " + goodResult);
// 3. Use primitive streams for better performance
System.out.println("\n3. Primitive Streams:");
int totalAge = people.stream()
.mapToInt(Person::getAge)  // Better performance
.sum();
System.out.println("Total age: " + totalAge);
// 4. Avoid nested streams when possible
System.out.println("\n4. Avoid Nested Streams:");
List<List<String>> nestedData = Arrays.asList(
Arrays.asList("A", "B"),
Arrays.asList("C", "D"),
Arrays.asList("E", "F")
);
// Good: use flatMap
List<String> flattened = nestedData.stream()
.flatMap(List::stream)
.collect(Collectors.toList());
System.out.println("Flattened: " + flattened);
// 5. Use collectors for mutable reduction
System.out.println("\n5. Proper Collection:");
// Good: use collector
Set<String> nameSet = people.stream()
.map(Person::getName)
.collect(Collectors.toCollection(TreeSet::new));
// Bad: manual collection
Set<String> manualSet = new TreeSet<>();
people.stream()
.map(Person::getName)
.forEach(manualSet::add);  // Avoid: manual collection
}
public static void performanceConsiderations() {
System.out.println("\n=== Performance Considerations ===");
// 1. Use parallel streams appropriately
List<Integer> largeList = IntStream.range(1, 1_000_000)
.boxed()
.collect(Collectors.toList());
long start = System.currentTimeMillis();
long sequentialSum = largeList.stream()
.mapToInt(Integer::intValue)
.sum();
long sequentialTime = System.currentTimeMillis() - start;
start = System.currentTimeMillis();
long parallelSum = largeList.parallelStream()
.mapToInt(Integer::intValue)
.sum();
long parallelTime = System.currentTimeMillis() - start;
System.out.println("Sequential sum: " + sequentialSum + " in " + sequentialTime + "ms");
System.out.println("Parallel sum: " + parallelSum + " in " + parallelTime + "ms");
// 2. Avoid unnecessary boxing
System.out.println("\n2. Avoid Boxing:");
long primitiveTime = measureTime(() -> 
IntStream.range(1, 1_000_000).sum());
long boxedTime = measureTime(() -> 
IntStream.range(1, 1_000_000).boxed()
.mapToInt(Integer::intValue).sum());
System.out.println("Primitive stream time: " + primitiveTime + "ns");
System.out.println("Boxed stream time: " + boxedTime + "ns");
}
public static void readabilityTips() {
System.out.println("\n=== Readability Tips ===");
List<Person> people = Arrays.asList(
new Person("John", 25),
new Person("Jane", 30),
new Person("Jack", 35)
);
// Good: break complex streams into steps
Predicate<Person> isAdult = p -> p.getAge() >= 18;
Function<Person, String> getName = Person::getName;
List<String> adultNames = people.stream()
.filter(isAdult)
.map(getName)
.collect(Collectors.toList());
System.out.println("Adult names: " + adultNames);
// Use meaningful variable names
List<String> namesStartingWithJ = people.stream()
.filter(person -> person.getName().startsWith("J"))
.map(Person::getName)
.sorted()
.collect(Collectors.toList());
System.out.println("Names starting with J: " + namesStartingWithJ);
}
private static long measureTime(Runnable operation) {
long start = System.nanoTime();
operation.run();
return System.nanoTime() - start;
}
public static void commonPitfalls() {
System.out.println("\n=== Common Pitfalls ===");
// 1. Reusing streams
Stream<String> stream = Stream.of("A", "B", "C");
long count = stream.count();
// stream.forEach(System.out::println);  // IllegalStateException!
// 2. Infinite streams without limit
// Stream.generate(() -> "data").forEach(System.out::println);  // Infinite!
// 3. Modifying source collection
List<String> source = new ArrayList<>(Arrays.asList("A", "B", "C"));
List<String> result = source.stream()
.peek(item -> {
if ("B".equals(item)) {
source.add("D");  // Concurrent modification risk!
}
})
.collect(Collectors.toList());
System.out.println("Modified source size: " + source.size());
}
public static void main(String[] args) {
demonstrateBestPractices();
performanceConsiderations();
readabilityTips();
commonPitfalls();
}
}

Summary

Key Stream API Concepts:

  1. Stream Creation: From collections, arrays, generators
  2. Intermediate Operations: filter, map, sorted, distinct (lazy)
  3. Terminal Operations: collect, forEach, reduce (eager)
  4. Collectors: toList, toSet, groupingBy, partitioningBy
  5. Parallel Streams: For CPU-intensive operations on large datasets

Best Practices:

  • Use method references where possible
  • Prefer primitive streams for better performance
  • Use parallel streams judiciously
  • Avoid stateful operations in streams
  • Keep streams readable and maintainable

The Stream API provides a powerful, declarative way to process data in Java, enabling more readable, maintainable, and often more performant code compared to traditional imperative approaches.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper