google-site-verification: google61fe8ba583a51912.html
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