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.