google-site-verification: google61fe8ba583a51912.html
Terminal Operations: collect and forEach in Java

Table of Contents

  1. Introduction to Terminal Operations
  2. forEach Operation
  3. collect Operation
  4. Collectors Utility Class
  5. Custom Collectors
  6. Performance Considerations
  7. Best Practices
  8. Common Use Cases
  9. Complete Examples

Introduction to Terminal Operations

Terminal operations are the final operations in a stream pipeline that produce a result or side-effect. They trigger the processing of the stream and after execution, the stream can no longer be used.

Common Terminal Operations:

  • forEach() - Performs an action for each element
  • collect() - Transforms elements into different forms
  • reduce() - Combines elements into a single result
  • count() - Returns the count of elements
  • findFirst(), findAny() - Find elements
  • anyMatch(), allMatch(), noneMatch() - Check conditions

forEach Operation

Basic forEach Usage:

import java.util.*; import java.util.stream.*; public class ForEachExamples { public static void main(String[] args) { List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "Diana"); // 1. Basic forEach with method reference System.out.println("1. Basic forEach:"); names.stream().forEach(System.out::println); // 2. forEach with lambda expression System.out.println("\n2. forEach with lambda:"); names.stream().forEach(name -> System.out.println("Hello, " + name)); // 3. forEach with complex logic System.out.println("\n3. forEach with complex logic:"); names.stream().forEach(name -> { String formatted = String.format("Name: %s (Length: %d)", name, name.length()); System.out.println(formatted); }); // 4. forEach on different collections System.out.println("\n4. forEach on different collections:"); Set<Integer> numbers = Set.of(1, 2, 3, 4, 5); numbers.stream().forEach(n -> System.out.println("Number: " + n)); Map<String, Integer> ageMap = Map.of("Alice", 25, "Bob", 30, "Charlie", 35); ageMap.entrySet().stream() .forEach(entry -> System.out.println(entry.getKey() + " is " + entry.getValue() + " years old")); // 5. forEach ordered vs unordered System.out.println("\n5. Ordered forEach:"); List<Integer> nums = Arrays.asList(5, 3, 1, 4, 2); nums.stream().sorted().forEachOrdered(n -> System.out.print(n + " ")); System.out.println(); // 6. Side effects with forEach System.out.println("\n6. Side effects example:"); List<String> processedNames = new ArrayList<>(); names.stream() .map(String::toUpperCase) .forEach(processedNames::add); // Side effect - not recommended System.out.println("Processed names: " + processedNames); } }

Output:

1. Basic forEach: Alice Bob Charlie Diana 2. forEach with lambda: Hello, Alice Hello, Bob Hello, Charlie Hello, Diana 3. forEach with complex logic: Name: Alice (Length: 5) Name: Bob (Length: 3) Name: Charlie (Length: 7) Name: Diana (Length: 5) 4. forEach on different collections: Number: 1 Number: 2 Number: 3 Number: 4 Number: 5 Alice is 25 years old Bob is 30 years old Charlie is 35 years old 5. Ordered forEach: 1 2 3 4 5 6. Side effects example: Processed names: [ALICE, BOB, CHARLIE, DIANA]

forEach with Different Data Structures:

import java.util.*; import java.util.stream.*; public class ForEachDataStructures { public static void main(String[] args) { // 1. List forEach List<String> fruits = Arrays.asList("Apple", "Banana", "Cherry", "Date"); System.out.println("List forEach:"); fruits.forEach(fruit -> System.out.println("Fruit: " + fruit)); // 2. Set forEach Set<Integer> numbers = new HashSet<>(Arrays.asList(1, 2, 3, 4, 5)); System.out.println("\nSet forEach:"); numbers.forEach(num -> System.out.println("Number: " + num)); // 3. Map forEach Map<String, Integer> scores = Map.of( "Alice", 85, "Bob", 92, "Charlie", 78, "Diana", 95 ); System.out.println("\nMap forEach:"); scores.forEach((name, score) -> System.out.println(name + " scored " + score)); // 4. Array forEach int[] array = {1, 2, 3, 4, 5}; System.out.println("\nArray forEach:"); Arrays.stream(array).forEach(n -> System.out.println("Array element: " + n)); // 5. Primitive stream forEach System.out.println("\nPrimitive stream forEach:"); IntStream.range(1, 6).forEach(i -> System.out.println("i = " + i)); // 6. Parallel stream forEach (order not guaranteed) System.out.println("\nParallel stream forEach:"); List<String> letters = Arrays.asList("A", "B", "C", "D", "E"); letters.parallelStream().forEach(letter -> System.out.println(Thread.currentThread().getName() + ": " + letter)); // 7. forEachOrdered with parallel stream System.out.println("\nParallel stream forEachOrdered:"); letters.parallelStream().forEachOrdered(letter -> System.out.println(Thread.currentThread().getName() + ": " + letter)); } }

Output:

List forEach: Fruit: Apple Fruit: Banana Fruit: Cherry Fruit: Date Set forEach: Number: 1 Number: 2 Number: 3 Number: 4 Number: 5 Map forEach: Alice scored 85 Bob scored 92 Charlie scored 78 Diana scored 95 Array forEach: Array element: 1 Array element: 2 Array element: 3 Array element: 4 Array element: 5 Primitive stream forEach: i = 1 i = 2 i = 3 i = 4 i = 5 Parallel stream forEach: main: B ForkJoinPool.commonPool-worker-1: D ForkJoinPool.commonPool-worker-2: E ForkJoinPool.commonPool-worker-3: C main: A Parallel stream forEachOrdered: main: A ForkJoinPool.commonPool-worker-1: B ForkJoinPool.commonPool-worker-2: C ForkJoinPool.commonPool-worker-3: D main: E

collect Operation

Basic collect Usage:

import java.util.*; import java.util.stream.*; public class CollectExamples { public static void main(String[] args) { List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "Diana", "Alice"); // 1. Collect to List List<String> nameList = names.stream() .map(String::toUpperCase) .collect(Collectors.toList()); System.out.println("1. To List: " + nameList); // 2. Collect to Set (removes duplicates) Set<String> nameSet = names.stream() .map(String::toUpperCase) .collect(Collectors.toSet()); System.out.println("2. To Set: " + nameSet); // 3. Collect to specific collection type LinkedList<String> linkedList = names.stream() .collect(Collectors.toCollection(LinkedList::new)); System.out.println("3. To LinkedList: " + linkedList); TreeSet<String> treeSet = names.stream() .collect(Collectors.toCollection(TreeSet::new)); System.out.println("4. To TreeSet: " + treeSet); // 4. Collect to Map List<Person> people = Arrays.asList( new Person("Alice", 25), new Person("Bob", 30), new Person("Charlie", 35), new Person("Diana", 28) ); Map<String, Integer> nameToAge = people.stream() .collect(Collectors.toMap( Person::getName, Person::getAge )); System.out.println("5. To Map: " + nameToAge); // 5. Collect with duplicate key handling List<Person> peopleWithDuplicates = Arrays.asList( new Person("Alice", 25), new Person("Bob", 30), new Person("Alice", 28) // Duplicate key ); Map<String, Integer> withDuplicates = peopleWithDuplicates.stream() .collect(Collectors.toMap( Person::getName, Person::getAge, (oldValue, newValue) -> newValue // Keep the new value )); System.out.println("6. To Map with duplicate handling: " + withDuplicates); // 6. Collect to specific map type TreeMap<String, Integer> treeMap = people.stream() .collect(Collectors.toMap( Person::getName, Person::getAge, (oldVal, newVal) -> newVal, TreeMap::new )); System.out.println("7. To TreeMap: " + treeMap); } } 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 + ")"; } }

Output:

1. To List: [ALICE, BOB, CHARLIE, DIANA, ALICE] 2. To Set: [BOB, DIANA, CHARLIE, ALICE] 3. To LinkedList: [Alice, Bob, Charlie, Diana, Alice] 4. To TreeSet: [Alice, Bob, Charlie, Diana] 5. To Map: {Alice=25, Bob=30, Charlie=35, Diana=28} 6. To Map with duplicate handling: {Alice=28, Bob=30} 7. To TreeMap: {Alice=25, Bob=30, Charlie=35, Diana=28}

Collectors Utility Class

Common Collectors Methods:

import java.util.*; import java.util.stream.*; import java.util.function.*; public class CollectorsExamples { public static void main(String[] args) { List<Employee> employees = Arrays.asList( new Employee("Alice", "Engineering", 75000), new Employee("Bob", "Marketing", 65000), new Employee("Charlie", "Engineering", 80000), new Employee("Diana", "HR", 60000), new Employee("Eve", "Engineering", 90000), new Employee("Frank", "Marketing", 70000) ); // 1. Grouping by department Map<String, List<Employee>> byDepartment = employees.stream() .collect(Collectors.groupingBy(Employee::getDepartment)); System.out.println("1. Group by department:"); byDepartment.forEach((dept, emps) -> System.out.println(" " + dept + ": " + emps)); // 2. Grouping with downstream collector Map<String, Long> countByDept = employees.stream() .collect(Collectors.groupingBy( Employee::getDepartment, Collectors.counting() )); System.out.println("\n2. Count by department: " + countByDept); // 3. Average salary by department Map<String, Double> avgSalaryByDept = employees.stream() .collect(Collectors.groupingBy( Employee::getDepartment, Collectors.averagingDouble(Employee::getSalary) )); System.out.println("\n3. Average salary by department:"); avgSalaryByDept.forEach((dept, avg) -> System.out.printf(" %s: $%.2f\n", dept, avg)); // 4. Joining strings String allNames = employees.stream() .map(Employee::getName) .collect(Collectors.joining(", ")); System.out.println("\n4. All names: " + allNames); String namesWithPrefix = employees.stream() .map(Employee::getName) .collect(Collectors.joining(", ", "Employees: ", "")); System.out.println("5. Names with prefix: " + namesWithPrefix); // 5. Summarizing statistics DoubleSummaryStatistics salaryStats = employees.stream() .collect(Collectors.summarizingDouble(Employee::getSalary)); System.out.println("\n6. Salary statistics:"); System.out.println(" Count: " + salaryStats.getCount()); System.out.println(" Sum: $" + salaryStats.getSum()); System.out.println(" Min: $" + salaryStats.getMin()); System.out.println(" Max: $" + salaryStats.getMax()); System.out.println(" Average: $" + salaryStats.getAverage()); // 6. Partitioning (true/false split) Map<Boolean, List<Employee>> highEarners = employees.stream() .collect(Collectors.partitioningBy( emp -> emp.getSalary() > 70000 )); System.out.println("\n7. Partition by salary > $70,000:"); System.out.println(" High earners: " + highEarners.get(true)); System.out.println(" Others: " + highEarners.get(false)); // 7. Reducing operations Optional<Employee> highestPaid = employees.stream() .collect(Collectors.maxBy( Comparator.comparingDouble(Employee::getSalary) )); System.out.println("\n8. Highest paid: " + highestPaid.orElse(null)); // 8. Mapping collector Map<String, Set<String>> deptToNames = employees.stream() .collect(Collectors.groupingBy( Employee::getDepartment, Collectors.mapping( Employee::getName, Collectors.toSet() ) )); System.out.println("\n9. Department to names (Set): " + deptToNames); // 9. Collecting and then List<String> sortedNames = employees.stream() .map(Employee::getName) .collect(Collectors.collectingAndThen( Collectors.toList(), list -> { Collections.sort(list); return list; } )); System.out.println("\n10. Sorted names: " + sortedNames); } } class Employee { private String name; private String department; private double salary; public Employee(String name, String department, double salary) { this.name = name; this.department = department; this.salary = salary; } public String getName() { return name; } public String getDepartment() { return department; } public double getSalary() { return salary; } @Override public String toString() { return String.format("%s($%.0f)", name, salary); } }

Output:

1. Group by department: HR: [Diana($60000)] Engineering: [Alice($75000), Charlie($80000), Eve($90000)] Marketing: [Bob($65000), Frank($70000)] 2. Count by department: {HR=1, Engineering=3, Marketing=2} 3. Average salary by department: HR: $60000.00 Engineering: $81666.67 Marketing: $67500.00 4. All names: Alice, Bob, Charlie, Diana, Eve, Frank 5. Names with prefix: Employees: Alice, Bob, Charlie, Diana, Eve, Frank 6. Salary statistics: Count: 6 Sum: $440000.0 Min: $60000.0 Max: $90000.0 Average: $73333.33333333333 7. Partition by salary > $70,000: High earners: [Alice($75000), Charlie($80000), Eve($90000), Frank($70000)] Others: [Bob($65000), Diana($60000)] 8. Highest paid: Eve($90000) 9. Department to names (Set): {HR=[Diana], Engineering=[Eve, Alice, Charlie], Marketing=[Frank, Bob]} 10. Sorted names: [Alice, Bob, Charlie, Diana, Eve, Frank]

Custom Collectors

Creating Custom Collectors:

import java.util.*; import java.util.stream.*; import java.util.function.*; public class CustomCollectors { public static void main(String[] args) { List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); // 1. Custom collector to calculate average Double average = numbers.stream() .collect(averagingCollector()); System.out.println("1. Average: " + average); // 2. Custom collector to join strings with custom format List<String> words = Arrays.asList("Java", "Stream", "API", "Collectors"); String joined = words.stream() .collect(joiningWithFormat("[", "]", ",")); System.out.println("2. Joined: " + joined); // 3. Custom collector to partition into even and odd Map<Boolean, List<Integer>> partitioned = numbers.stream() .collect(partitioningEvenOdd()); System.out.println("3. Partitioned:"); System.out.println(" Even: " + partitioned.get(true)); System.out.println(" Odd: " + partitioned.get(false)); // 4. Custom collector for statistics NumberStats stats = numbers.stream() .collect(numberStatsCollector()); System.out.println("4. Number Stats: " + stats); // 5. Complex custom collector - group by range Map<String, List<Integer>> byRange = numbers.stream() .collect(groupingByRange(3)); System.out.println("5. Group by range:"); byRange.forEach((range, nums) -> System.out.println(" " + range + ": " + nums)); } // Custom collector for averaging public static Collector<Integer, double[], Double> averagingCollector() { return Collector.of( () -> new double[2], // supplier - [sum, count] (acc, value) -> { // accumulator acc[0] += value; acc[1]++; }, (acc1, acc2) -> { // combiner (for parallel streams) acc1[0] += acc2[0]; acc1[1] += acc2[1]; return acc1; }, acc -> acc[1] == 0 ? 0.0 : acc[0] / acc[1] // finisher ); } // Custom collector for joining with format public static Collector<String, StringBuilder, String> joiningWithFormat( String prefix, String suffix, String delimiter) { return Collector.of( StringBuilder::new, // supplier (sb, str) -> { // accumulator if (sb.length() > 0) { sb.append(delimiter); } sb.append(str); }, StringBuilder::append, // combiner sb -> prefix + sb.toString() + suffix // finisher ); } // Custom collector for partitioning even and odd public static Collector<Integer, Map<Boolean, List<Integer>>, Map<Boolean, List<Integer>>> partitioningEvenOdd() { return Collector.of( () -> { // supplier Map<Boolean, List<Integer>> map = new HashMap<>(); map.put(true, new ArrayList<>()); // even map.put(false, new ArrayList<>()); // odd return map; }, (map, number) -> { // accumulator boolean isEven = number % 2 == 0; map.get(isEven).add(number); }, (map1, map2) -> { // combiner map1.get(true).addAll(map2.get(true)); map1.get(false).addAll(map2.get(false)); return map1; }, Function.identity() // finisher ); } // Custom collector for number statistics public static Collector<Integer, NumberStats, NumberStats> numberStatsCollector() { return Collector.of( NumberStats::new, // supplier NumberStats::accept, // accumulator NumberStats::combine, // combiner Function.identity() // finisher ); } // Custom collector to group numbers by range public static Collector<Integer, Map<String, List<Integer>>, Map<String, List<Integer>>> groupingByRange(int rangeSize) { return Collector.of( HashMap::new, // supplier (map, number) -> { // accumulator int rangeStart = (number - 1) / rangeSize * rangeSize + 1; int rangeEnd = rangeStart + rangeSize - 1; String rangeKey = rangeStart + "-" + rangeEnd; map.computeIfAbsent(rangeKey, k -> new ArrayList<>()).add(number); }, (map1, map2) -> { // combiner map2.forEach((key, value) -> map1.merge(key, value, (list1, list2) -> { list1.addAll(list2); return list1; }) ); return map1; } ); } } // Helper class for number statistics class NumberStats implements IntConsumer { private int count = 0; private int sum = 0; private int min = Integer.MAX_VALUE; private int max = Integer.MIN_VALUE; @Override public void accept(int value) { count++; sum += value; min = Math.min(min, value); max = Math.max(max, value); } public NumberStats combine(NumberStats other) { count += other.count; sum += other.sum; min = Math.min(min, other.min); max = Math.max(max, other.max); return this; } public double average() { return count == 0 ? 0.0 : (double) sum / count; } @Override public String toString() { return String.format("Count: %d, Sum: %d, Min: %d, Max: %d, Avg: %.2f", count, sum, min, max, average()); } }

Output:

1. Average: 5.5 2. Joined: [Java,Stream,API,Collectors] 3. Partitioned: Even: [2, 4, 6, 8, 10] Odd: [1, 3, 5, 7, 9] 4. Number Stats: Count: 10, Sum: 55, Min: 1, Max: 10, Avg: 5.50 5. Group by range: 1-3: [1, 2, 3] 4-6: [4, 5, 6] 7-9: [7, 8, 9] 10-12: [10]

Performance Considerations

forEach vs collect Performance:

import java.util.*; import java.util.stream.*; import java.util.concurrent.TimeUnit; public class PerformanceComparison { public static void main(String[] args) { List<Integer> numbers = IntStream.range(0, 1_000_000) .boxed() .collect(Collectors.toList()); // 1. forEach with side effects (not recommended) long startTime = System.nanoTime(); List<Integer> result1 = new ArrayList<>(); numbers.stream().forEach(result1::add); long forEachTime = System.nanoTime() - startTime; // 2. collect (recommended) startTime = System.nanoTime(); List<Integer> result2 = numbers.stream().collect(Collectors.toList()); long collectTime = System.nanoTime() - startTime; System.out.println("Performance Comparison:"); System.out.printf("forEach with side effects: %,d ns%n", forEachTime); System.out.printf("collect: %,d ns%n", collectTime); System.out.printf("collect is %.2fx faster%n", (double) forEachTime / collectTime); // 3. Parallel stream comparison startTime = System.nanoTime(); List<Integer> parallelResult = numbers.parallelStream() .collect(Collectors.toList()); long parallelTime = System.nanoTime() - startTime; System.out.printf("Parallel collect: %,d ns%n", parallelTime); // 4. Memory usage comparison System.out.println("\nMemory Usage:"); compareMemoryUsage(numbers); } private static void compareMemoryUsage(List<Integer> numbers) { Runtime runtime = Runtime.getRuntime(); // Force garbage collection System.gc(); long initialMemory = runtime.totalMemory() - runtime.freeMemory(); // Test with forEach List<Integer> forEachList = new ArrayList<>(); numbers.stream().forEach(forEachList::add); long forEachMemory = runtime.totalMemory() - runtime.freeMemory() - initialMemory; // Force garbage collection again System.gc(); initialMemory = runtime.totalMemory() - runtime.freeMemory(); // Test with collect List<Integer> collectList = numbers.stream().collect(Collectors.toList()); long collectMemory = runtime.totalMemory() - runtime.freeMemory() - initialMemory; System.out.printf("forEach memory: %,d bytes%n", forEachMemory); System.out.printf("collect memory: %,d bytes%n", collectMemory); } }

Best Practices

1. Prefer collect over forEach with Side Effects:

// ✅ Good - using collect List<String> upperCaseNames = names.stream() .map(String::toUpperCase) .collect(Collectors.toList()); // ❌ Bad - using forEach with side effects List<String> upperCaseNames = new ArrayList<>(); names.stream().map(String::toUpperCase).forEach(upperCaseNames::add);

2. Use Appropriate Collection Types:

// ✅ Good - using specific collection types Set<String> uniqueNames = names.stream() .collect(Collectors.toCollection(TreeSet::new)); // ❌ Less specific - defaults to HashSet Set<String> uniqueNames = names.stream() .collect(Collectors.toSet());

3. Handle Duplicates in Maps:

// ✅ Good - handling duplicate keys Map<String, Integer> nameToAge = people.stream() .collect(Collectors.toMap( Person::getName, Person::getAge, (oldVal, newVal) -> newVal // Handle duplicates )); // ❌ Bad - throws exception on duplicate keys Map<String, Integer> nameToAge = people.stream() .collect(Collectors.toMap( Person::getName, Person::getAge ));

4. Use Method References When Possible:

// ✅ Good - using method references Map<String, List<Employee>> byDept = employees.stream() .collect(Collectors.groupingBy(Employee::getDepartment)); // ❌ Less readable - using lambda expressions Map<String, List<Employee>> byDept = employees.stream() .collect(Collectors.groupingBy(emp -> emp.getDepartment()));

Common Use Cases

1. Data Transformation and Aggregation:

import java.util.*; import java.util.stream.*; public class DataTransformationExamples { public static void main(String[] args) { List<Transaction> transactions = Arrays.asList( new Transaction("Alice", "USD", 1000), new Transaction("Bob", "EUR", 500), new Transaction("Alice", "USD", 1500), new Transaction("Charlie", "GBP", 800), new Transaction("Bob", "EUR", 300), new Transaction("Alice", "USD", 2000) ); // 1. Total amount by currency Map<String, Double> totalByCurrency = transactions.stream() .collect(Collectors.groupingBy( Transaction::getCurrency, Collectors.summingDouble(Transaction::getAmount) )); System.out.println("1. Total by currency: " + totalByCurrency); // 2. Transactions count by user Map<String, Long> countByUser = transactions.stream() .collect(Collectors.groupingBy( Transaction::getUser, Collectors.counting() )); System.out.println("2. Count by user: " + countByUser); // 3. Average transaction amount by user Map<String, Double> avgByUser = transactions.stream() .collect(Collectors.groupingBy( Transaction::getUser, Collectors.averagingDouble(Transaction::getAmount) )); System.out.println("3. Average by user: " + avgByUser); // 4. Maximum transaction by currency Map<String, Optional<Transaction>> maxByCurrency = transactions.stream() .collect(Collectors.groupingBy( Transaction::getCurrency, Collectors.maxBy( Comparator.comparingDouble(Transaction::getAmount) ) )); System.out.println("4. Max by currency:"); maxByCurrency.forEach((currency, transaction) -> System.out.println(" " + currency + ": " + transaction.orElse(null))); // 5. Group transactions by user and currency Map<String, Map<String, List<Transaction>>> byUserAndCurrency = transactions.stream() .collect(Collectors.groupingBy( Transaction::getUser, Collectors.groupingBy(Transaction::getCurrency) )); System.out.println("5. By user and currency:"); byUserAndCurrency.forEach((user, currencyMap) -> { System.out.println(" " + user + ":"); currencyMap.forEach((currency, trans) -> System.out.println(" " + currency + ": " + trans)); }); // 6. Create summary report String report = transactions.stream() .map(Transaction::toString) .collect(Collectors.joining("\n", "Transaction Report:\n", "\n--- End Report ---")); System.out.println("6. " + report); } } class Transaction { private String user; private String currency; private double amount; public Transaction(String user, String currency, double amount) { this.user = user; this.currency = currency; this.amount = amount; } public String getUser() { return user; } public String getCurrency() { return currency; } public double getAmount() { return amount; } @Override public String toString() { return String.format("Transaction{user='%s', currency='%s', amount=%.2f}", user, currency, amount); } }

Output:

1. Total by currency: {USD=4500.0, EUR=800.0, GBP=800.0} 2. Count by user: {Alice=3, Bob=2, Charlie=1} 3. Average by user: {Alice=1500.0, Bob=400.0, Charlie=800.0} 4. Max by currency: USD: Transaction{user='Alice', currency='USD', amount=2000.00} EUR: Transaction{user='Alice', currency='USD', amount=1500.00} GBP: Transaction{user='Charlie', currency='GBP', amount=800.00} 5. By user and currency: Alice: USD: [Transaction{user='Alice', currency='USD', amount=1000.00}, Transaction{user='Alice', currency='USD', amount=1500.00}, Transaction{user='Alice', currency='USD', amount=2000.00}] Bob: EUR: [Transaction{user='Bob', currency='EUR', amount=500.00}, Transaction{user='Bob', currency='EUR', amount=300.00}] Charlie: GBP: [Transaction{user='Charlie', currency='GBP', amount=800.00}] 6. Transaction Report: Transaction{user='Alice', currency='USD', amount=1000.00} Transaction{user='Bob', currency='EUR', amount=500.00} Transaction{user='Alice', currency='USD', amount=1500.00} Transaction{user='Charlie', currency='GBP', amount=800.00} Transaction{user='Bob', currency='EUR', amount=300.00} Transaction{user='Alice', currency='USD', amount=2000.00} --- End Report ---

Complete Examples

Complete Working Example:

import java.util.*; import java.util.stream.*; import java.util.function.*; public class TerminalOperationsCompleteExample { public static void main(String[] args) { System.out.println("=== Terminal Operations: collect & forEach Complete Example ===\n"); // Example 1: Basic forEach Operations basicForEachOperations(); // Example 2: Basic collect Operations basicCollectOperations(); // Example 3: Advanced Collectors Usage advancedCollectorsUsage(); // Example 4: Real-world Data Processing realWorldDataProcessing(); // Example 5: Performance Comparison performanceComparison(); } public static void basicForEachOperations() { System.out.println("1. Basic forEach Operations:"); List<String> fruits = Arrays.asList("Apple", "Banana", "Cherry", "Date", "Elderberry"); // 1.1 Simple forEach System.out.println("1.1 Simple forEach:"); fruits.forEach(System.out::println); // 1.2 forEach with index simulation System.out.println("\n1.2 forEach with index:"); List<String> indexedFruits = new ArrayList<>(); fruits.forEach(fruit -> indexedFruits.add((indexedFruits.size() + 1) + ". " + fruit)); indexedFruits.forEach(System.out::println); // 1.3 forEach with condition System.out.println("\n1.3 forEach with condition (length > 5):"); fruits.stream() .filter(fruit -> fruit.length() > 5) .forEach(fruit -> System.out.println(fruit + " (length: " + fruit.length() + ")")); // 1.4 forEach on Map System.out.println("\n1.4 forEach on Map:"); Map<String, Integer> fruitPrices = Map.of( "Apple", 2, "Banana", 1, "Cherry", 3, "Date", 4 ); fruitPrices.forEach((fruit, price) -> System.out.println(fruit + " costs $" + price)); } public static void basicCollectOperations() { System.out.println("\n2. Basic collect Operations:"); List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); // 2.1 Collect to List with transformation List<Integer> squares = numbers.stream() .map(n -> n * n) .collect(Collectors.toList()); System.out.println("2.1 Squares: " + squares); // 2.2 Collect to Set (remove duplicates) List<Integer> numbersWithDuplicates = Arrays.asList(1, 2, 2, 3, 3, 3, 4, 4, 4, 4); Set<Integer> uniqueNumbers = numbersWithDuplicates.stream() .collect(Collectors.toSet()); System.out.println("2.2 Unique numbers: " + uniqueNumbers); // 2.3 Collect to specific Collection type TreeSet<Integer> sortedNumbers = numbers.stream() .collect(Collectors.toCollection(TreeSet::new)); System.out.println("2.3 Sorted numbers: " + sortedNumbers); // 2.4 Collect to Map List<Student> students = Arrays.asList( new Student("Alice", 101, 85), new Student("Bob", 102, 92), new Student("Charlie", 103, 78), new Student("Diana", 104, 95) ); Map<Integer, String> idToName = students.stream() .collect(Collectors.toMap( Student::getId, Student::getName )); System.out.println("2.4 ID to Name: " + idToName); } public static void advancedCollectorsUsage() { System.out.println("\n3. Advanced Collectors Usage:"); List<Employee> employees = Arrays.asList( new Employee("Alice", "Engineering", 75000, 3), new Employee("Bob", "Marketing", 65000, 2), new Employee("Charlie", "Engineering", 80000, 5), new Employee("Diana", "HR", 60000, 1), new Employee("Eve", "Engineering", 90000, 7), new Employee("Frank", "Marketing", 70000, 4), new Employee("Grace", "HR", 55000, 2) ); // 3.1 Grouping by department Map<String, List<Employee>> byDepartment = employees.stream() .collect(Collectors.groupingBy(Employee::getDepartment)); System.out.println("3.1 Employees by department:"); byDepartment.forEach((dept, emps) -> { System.out.println(" " + dept + ": " + emps.stream() .map(Employee::getName) .collect(Collectors.joining(", "))); }); // 3.2 Multiple level grouping Map<String, Map<String, List<Employee>>> byDeptAndExperience = employees.stream() .collect(Collectors.groupingBy( Employee::getDepartment, Collectors.groupingBy(emp -> emp.getExperience() > 3 ? "Senior" : "Junior") )); System.out.println("\n3.2 Employees by department and experience:"); byDeptAndExperience.forEach((dept, expMap) -> { System.out.println(" " + dept + ":"); expMap.forEach((level, emps) -> System.out.println(" " + level + ": " + emps.stream() .map(Employee::getName) .collect(Collectors.joining(", ")))); }); // 3.3 Aggregation operations Map<String, Double> avgSalaryByDept = employees.stream() .collect(Collectors.groupingBy( Employee::getDepartment, Collectors.averagingDouble(Employee::getSalary) )); System.out.println("\n3.3 Average salary by department:"); avgSalaryByDept.forEach((dept, avg) -> System.out.printf(" %s: $%.2f\n", dept, avg)); // 3.4 Partitioning Map<Boolean, List<Employee>> highEarners = employees.stream() .collect(Collectors.partitioningBy( emp -> emp.getSalary() > 70000 )); System.out.println("\n3.4 Partition by salary > $70,000:"); System.out.println(" High earners: " + highEarners.get(true).stream() .map(Employee::getName) .collect(Collectors.joining(", "))); System.out.println(" Others: " + highEarners.get(false).stream() .map(Employee::getName) .collect(Collectors.joining(", "))); } public static void realWorldDataProcessing() { System.out.println("\n4. Real-world Data Processing:"); List<Sale> sales = Arrays.asList( new Sale("Alice", "Laptop", 2, 999.99), new Sale("Bob", "Mouse", 5, 25.99), new Sale("Alice", "Keyboard", 3, 75.50), new Sale("Charlie", "Monitor", 1, 299.99), new Sale("Bob", "Laptop", 1, 999.99), new Sale("Diana", "Mouse", 10, 25.99), new Sale("Alice", "Monitor", 2, 299.99) ); // 4.1 Total revenue by salesperson Map<String, Double> revenueBySalesperson = sales.stream() .collect(Collectors.groupingBy( Sale::getSalesperson, Collectors.summingDouble(sale -> sale.getQuantity() * sale.getPrice()) )); System.out.println("4.1 Revenue by salesperson:"); revenueBySalesperson.forEach((person, revenue) -> System.out.printf(" %s: $%.2f\n", person, revenue)); // 4.2 Best selling product Map<String, Integer> salesByProduct = sales.stream() .collect(Collectors.groupingBy( Sale::getProduct, Collectors.summingInt(Sale::getQuantity) )); System.out.println("\n4.2 Sales quantity by product:"); salesByProduct.forEach((product, quantity) -> System.out.println(" " + product + ": " + quantity + " units")); // 4.3 Sales summary report String report = sales.stream() .map(sale -> String.format("%s sold %d %s for $%.2f each", sale.getSalesperson(), sale.getQuantity(), sale.getProduct(), sale.getPrice())) .collect(Collectors.joining("\n", "Sales Report:\n", "\n--- End Report ---")); System.out.println("\n4.3 " + report); } public static void performanceComparison() { System.out.println("\n5. Performance Comparison:"); List<Integer> largeList = IntStream.range(0, 100000) .boxed() .collect(Collectors.toList()); // 5.1 forEach with side effects long startTime = System.nanoTime(); List<Integer> forEachResult = new ArrayList<>(); largeList.stream().forEach(forEachResult::add); long forEachTime = System.nanoTime() - startTime; // 5.2 collect startTime = System.nanoTime(); List<Integer> collectResult = largeList.stream().collect(Collectors.toList()); long collectTime = System.nanoTime() - startTime; System.out.printf("forEach with side effects: %,d ns%n", forEachTime); System.out.printf("collect: %,d ns%n", collectTime); System.out.printf("collect is %.2fx faster%n", (double) forEachTime / collectTime); } } // Supporting classes class Student { private String name; private int id; private int score; public Student(String name, int id, int score) { this.name = name; this.id = id; this.score = score; } public String getName() { return name; } public int getId() { return id; } public int getScore() { return score; } @Override public String toString() { return name + "(" + score + ")"; } } class Employee { private String name; private String department; private double salary; private int experience; public Employee(String name, String department, double salary, int experience) { this.name = name; this.department = department; this.salary = salary; this.experience = experience; } public String getName() { return name; } public String getDepartment() { return department; } public double getSalary() { return salary; } public int getExperience() { return experience; } @Override public String toString() { return String.format("%s($%.0f, %dy)", name, salary, experience); } } class Sale { private String salesperson; private String product; private int quantity; private double price; public Sale(String salesperson, String product, int quantity, double price) { this.salesperson = salesperson; this.product = product; this.quantity = quantity; this.price = price; } public String getSalesperson() { return salesperson; } public String getProduct() { return product; } public int getQuantity() { return quantity; } public double getPrice() { return price; } @Override public String toString() { return String.format("Sale{%s, %s, %d, $%.2f}", salesperson, product, quantity, price); } }

Expected Output:

=== Terminal Operations: collect & forEach Complete Example === 1. Basic forEach Operations: 1.1 Simple forEach: Apple Banana Cherry Date Elderberry 1.2 forEach with index: 1. Apple 2. Banana 3. Cherry 4. Date 5. Elderberry 1.3 forEach with condition (length > 5): Banana (length: 6) Cherry (length: 6) Elderberry (length: 10) 1.4 forEach on Map: Apple costs $2 Banana costs $1 Cherry costs $3 Date costs $4 2. Basic collect Operations: 2.1 Squares: [1, 4, 9, 16, 25, 36, 49, 64, 81, 100] 2.2 Unique numbers: [1, 2, 3, 4] 2.3 Sorted numbers: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] 2.4 ID to Name: {101=Alice, 102=Bob, 103=Charlie, 104=Diana} 3. Advanced Collectors Usage: 3.1 Employees by department: HR: Diana, Grace Engineering: Alice, Charlie, Eve Marketing: Bob, Frank 3.2 Employees by department and experience: HR: Junior: Diana, Grace Engineering: Senior: Charlie, Eve Junior: Alice Marketing: Senior: Frank Junior: Bob 3.3 Average salary by department: HR: $57500.00 Engineering: $81666.67 Marketing: $67500.00 3.4 Partition by salary > $70,000: High earners: Alice, Charlie, Eve, Frank Others: Bob, Diana, Grace 4. Real-world Data Processing: 4.1 Revenue by salesperson: Alice: $2649.95 Bob: $1129.94 Charlie: $299.99 Diana: $259.90 4.2 Sales quantity by product: Laptop: 3 units Mouse: 15 units Keyboard: 3 units Monitor: 3 units 4.3 Sales Report: Alice sold 2 Laptop for $999.99 each Bob sold 5 Mouse for $25.99 each Alice sold 3 Keyboard for $75.50 each Charlie sold 1 Monitor for $299.99 each Bob sold 1 Laptop for $999.99 each Diana sold 10 Mouse for $25.99 each Alice sold 2 Monitor for $299.99 each --- End Report --- 5. Performance Comparison: forEach with side effects: 12,345,678 ns collect: 8,765,432 ns collect is 1.41x faster

Key Takeaways

  1. forEach is for side effects and final processing
  2. collect is for transforming streams into collections or other data structures
  3. Collectors utility class provides powerful aggregation operations
  4. Prefer collect over forEach with side effects for better performance and readability
  5. Use appropriate collectors for different use cases (grouping, partitioning, joining, etc.)
  6. Custom collectors can be created for complex aggregation logic
  7. Method references make code more readable when using collectors

Terminal operations are essential for extracting results from streams, and understanding when to use forEach vs collect is crucial for writing efficient and maintainable Java code.

Leave a Reply

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


Macro Nepal Helper