Table of Contents
- Introduction to Stream API
- Stream Creation
- Intermediate Operations
- Terminal Operations
- Collectors
- Parallel Streams
- Advanced Stream Operations
- 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:
- Stream Creation: From collections, arrays, generators
- Intermediate Operations: filter, map, sorted, distinct (lazy)
- Terminal Operations: collect, forEach, reduce (eager)
- Collectors: toList, toSet, groupingBy, partitioningBy
- 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.