Foreach

1. What is forEach?

Imagine you are a teacher handing back graded papers. You pick up each paper from the stack, call the student’s name, and hand it over. You do not care about the paper’s position in the stack — you just process each one in order until the stack is empty. That is what forEach() does: it takes every element in a collection and performs an action on it.

Introduced in Java 8, forEach() is a default method on the Iterable interface. Since all collections (List, Set, Queue) implement Iterable, every collection in Java gets forEach() for free. Map also has its own forEach() method that accepts both key and value.

The method signature:

// On Iterable (List, Set, Queue)
default void forEach(Consumer action)

// On Map
default void forEach(BiConsumer action)

It takes a Consumer (a functional interface that accepts one argument and returns nothing) and applies it to each element.

1.1 Internal vs External Iteration

The fundamental difference between forEach() and traditional loops is who controls the iteration:

Aspect External Iteration (for loop) Internal Iteration (forEach)
Who controls iteration? You (the programmer) The collection itself
Index management You manage the index variable No index to manage
How elements are accessed You call get(i) or use an iterator Elements are passed to your action
Parallelism potential Difficult to parallelize Collection could parallelize internally
Style Imperative (how to iterate) Declarative (what to do with each element)
import java.util.List;

public class InternalVsExternal {
    public static void main(String[] args) {
        List languages = List.of("Java", "Python", "Go", "Rust");

        // External iteration: YOU control the loop
        System.out.println("--- External (for-i) ---");
        for (int i = 0; i < languages.size(); i++) {
            System.out.println(languages.get(i));
        }

        // External iteration: enhanced for-each (still external, compiler converts to iterator)
        System.out.println("--- External (for-each) ---");
        for (String lang : languages) {
            System.out.println(lang);
        }

        // Internal iteration: THE COLLECTION controls the loop
        System.out.println("--- Internal (forEach) ---");
        languages.forEach(lang -> System.out.println(lang));

        // Even shorter with method reference
        System.out.println("--- Internal (method reference) ---");
        languages.forEach(System.out::println);
    }
}
// All four produce the same output:
// Java
// Python
// Go
// Rust

2. forEach on Lists

Lists are the most common collection type, and forEach() works naturally with them. Since List is ordered, forEach() processes elements in insertion order.

2.1 Basic Usage with Lambda

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class ForEachOnList {
    public static void main(String[] args) {

        // --- Basic lambda ---
        List fruits = List.of("Apple", "Banana", "Cherry", "Date");

        fruits.forEach(fruit -> System.out.println("Fruit: " + fruit));
        // Output:
        // Fruit: Apple
        // Fruit: Banana
        // Fruit: Cherry
        // Fruit: Date

        // --- Method reference ---
        System.out.println("\n--- Method reference ---");
        fruits.forEach(System.out::println);
        // Output:
        // Apple
        // Banana
        // Cherry
        // Date

        // --- Multi-line lambda (block body) ---
        System.out.println("\n--- Multi-line lambda ---");
        List numbers = List.of(1, 2, 3, 4, 5);
        numbers.forEach(n -> {
            int squared = n * n;
            System.out.println(n + " squared = " + squared);
        });
        // Output:
        // 1 squared = 1
        // 2 squared = 4
        // 3 squared = 9
        // 4 squared = 16
        // 5 squared = 25

        // --- Building a result (collecting into external variable) ---
        System.out.println("\n--- Accumulating results ---");
        List names = List.of("alice", "bob", "charlie");
        List uppercased = new ArrayList<>();
        names.forEach(name -> uppercased.add(name.toUpperCase()));
        System.out.println("Uppercased: " + uppercased);
        // Output: Uppercased: [ALICE, BOB, CHARLIE]
    }
}

2.2 Index Tracking with forEach

One limitation of forEach() is that it does not provide an index. If you need the index, you have several options:

import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.IntStream;

public class ForEachWithIndex {
    public static void main(String[] args) {
        List colors = List.of("Red", "Green", "Blue", "Yellow");

        // Option 1: AtomicInteger counter
        System.out.println("--- AtomicInteger ---");
        AtomicInteger index = new AtomicInteger(0);
        colors.forEach(color ->
            System.out.println(index.getAndIncrement() + ": " + color));
        // Output:
        // 0: Red
        // 1: Green
        // 2: Blue
        // 3: Yellow

        // Option 2: IntStream.range (preferred -- cleaner)
        System.out.println("\n--- IntStream.range ---");
        IntStream.range(0, colors.size())
                 .forEach(i -> System.out.println(i + ": " + colors.get(i)));
        // Output:
        // 0: Red
        // 1: Green
        // 2: Blue
        // 3: Yellow

        // Option 3: Just use a traditional for loop (simplest when you need the index)
        System.out.println("\n--- Traditional for (recommended for index access) ---");
        for (int i = 0; i < colors.size(); i++) {
            System.out.println(i + ": " + colors.get(i));
        }
        // Output: same as above
    }
}

Tip: If you need the index, a traditional for loop is usually clearer than hacking an index counter into forEach(). Use the right tool for the job.

3. forEach on Sets

Sets have no guaranteed ordering (except LinkedHashSet and TreeSet). When you call forEach() on a HashSet, the order of elements is unpredictable and may change between runs.

import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.TreeSet;

public class ForEachOnSets {
    public static void main(String[] args) {

        // --- HashSet: no ordering guarantee ---
        System.out.println("--- HashSet (unordered) ---");
        Set hashSet = new HashSet<>();
        hashSet.add("Banana");
        hashSet.add("Apple");
        hashSet.add("Cherry");
        hashSet.add("Date");

        hashSet.forEach(item -> System.out.println("  " + item));
        // Output order is NOT guaranteed -- could be:
        //   Apple
        //   Cherry
        //   Banana
        //   Date

        // --- LinkedHashSet: insertion order preserved ---
        System.out.println("\n--- LinkedHashSet (insertion order) ---");
        Set linkedSet = new LinkedHashSet<>();
        linkedSet.add("Banana");
        linkedSet.add("Apple");
        linkedSet.add("Cherry");
        linkedSet.add("Date");

        linkedSet.forEach(item -> System.out.println("  " + item));
        // Output: always in insertion order
        //   Banana
        //   Apple
        //   Cherry
        //   Date

        // --- TreeSet: sorted order ---
        System.out.println("\n--- TreeSet (sorted) ---");
        Set treeSet = new TreeSet<>();
        treeSet.add("Banana");
        treeSet.add("Apple");
        treeSet.add("Cherry");
        treeSet.add("Date");

        treeSet.forEach(item -> System.out.println("  " + item));
        // Output: always sorted alphabetically
        //   Apple
        //   Banana
        //   Cherry
        //   Date

        // --- Practical: counting unique characters ---
        System.out.println("\n--- Unique characters ---");
        String text = "hello world";
        Set uniqueChars = new LinkedHashSet<>();
        for (char c : text.toCharArray()) {
            uniqueChars.add(c);
        }
        System.out.print("Unique chars: ");
        uniqueChars.forEach(c -> System.out.print(c + " "));
        System.out.println();
        // Output: Unique chars: h e l o   w r d
    }
}

4. forEach on Maps

Map has its own forEach() method that accepts a BiConsumer, giving you direct access to both the key and value. Before Java 8, iterating over a map required calling entrySet() and manually extracting keys and values.

import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.TreeMap;

public class ForEachOnMaps {
    public static void main(String[] args) {

        Map scores = new LinkedHashMap<>();
        scores.put("Alice", 95);
        scores.put("Bob", 87);
        scores.put("Charlie", 92);
        scores.put("Diana", 78);

        // --- Before Java 8: verbose ---
        System.out.println("--- Before Java 8 ---");
        for (Map.Entry entry : scores.entrySet()) {
            System.out.println(entry.getKey() + ": " + entry.getValue());
        }
        // Output:
        // Alice: 95
        // Bob: 87
        // Charlie: 92
        // Diana: 78

        // --- Java 8 forEach with BiConsumer ---
        System.out.println("\n--- Java 8 forEach ---");
        scores.forEach((name, score) ->
            System.out.println(name + " scored " + score));
        // Output:
        // Alice scored 95
        // Bob scored 87
        // Charlie scored 92
        // Diana scored 78

        // --- Conditional logic inside forEach ---
        System.out.println("\n--- High scorers (90+) ---");
        scores.forEach((name, score) -> {
            if (score >= 90) {
                System.out.println(name + ": " + score + " (Excellent!)");
            }
        });
        // Output:
        // Alice: 95 (Excellent!)
        // Charlie: 92 (Excellent!)

        // --- Building a formatted report ---
        System.out.println("\n--- Formatted Report ---");
        Map config = new TreeMap<>();
        config.put("database.host", "localhost");
        config.put("database.port", "5432");
        config.put("database.name", "myapp");
        config.put("server.port", "8080");

        config.forEach((key, value) ->
            System.out.printf("  %-20s = %s%n", key, value));
        // Output (TreeMap sorts by key):
        //   database.host        = localhost
        //   database.name        = myapp
        //   database.port        = 5432
        //   server.port          = 8080

        // --- Iterating only keys or only values ---
        System.out.println("\n--- Keys only ---");
        scores.keySet().forEach(name -> System.out.println("Student: " + name));
        // Output:
        // Student: Alice
        // Student: Bob
        // Student: Charlie
        // Student: Diana

        System.out.println("\n--- Values only ---");
        scores.values().forEach(score -> System.out.println("Score: " + score));
        // Output:
        // Score: 95
        // Score: 87
        // Score: 92
        // Score: 78
    }
}

5. forEach with Streams

There are two ways to use forEach(): directly on the collection (collection.forEach()) and through a stream (collection.stream().forEach()). They look similar but have important differences.

5.1 collection.forEach() vs stream().forEach()

Aspect collection.forEach() stream().forEach()
Source Default method on Iterable Terminal operation on Stream
Order guarantee Follows the collection's iteration order No order guarantee (especially with parallel streams)
Can filter/map first? No (processes all elements as-is) Yes (chain with filter(), map(), etc.)
Use when... You want to perform an action on every element You need to transform or filter before acting
import java.util.List;

public class ForEachVsStreamForEach {
    public static void main(String[] args) {
        List names = List.of("Alice", "Bob", "Charlie", "Diana", "Eve");

        // collection.forEach() -- processes ALL elements
        System.out.println("--- collection.forEach() ---");
        names.forEach(name -> System.out.println("Hello, " + name));
        // Output: Hello, Alice ... Hello, Bob ... etc.

        // stream().forEach() -- can filter/map first
        System.out.println("\n--- stream().filter().forEach() ---");
        names.stream()
             .filter(name -> name.length() > 3)
             .map(String::toUpperCase)
             .forEach(name -> System.out.println("Hello, " + name));
        // Output:
        // Hello, ALICE
        // Hello, CHARLIE
        // Hello, DIANA

        // For simple iteration, collection.forEach() is preferred
        // For filtered/transformed iteration, stream().forEach() is needed
    }
}

5.2 forEachOrdered()

When using parallel streams, forEach() does not guarantee processing order. If order matters, use forEachOrdered() instead.

import java.util.List;

public class ForEachOrdered {
    public static void main(String[] args) {
        List letters = List.of("A", "B", "C", "D", "E", "F", "G", "H");

        // Parallel stream with forEach -- order is NOT guaranteed
        System.out.println("--- parallelStream().forEach() ---");
        letters.parallelStream()
               .forEach(letter -> System.out.print(letter + " "));
        System.out.println();
        // Output might be: E F G H A B C D (order varies each run)

        // Parallel stream with forEachOrdered -- order IS guaranteed
        System.out.println("--- parallelStream().forEachOrdered() ---");
        letters.parallelStream()
               .forEachOrdered(letter -> System.out.print(letter + " "));
        System.out.println();
        // Output: A B C D E F G H (always in order)

        // Note: forEachOrdered() with parallel stream may reduce
        // performance benefits of parallelism, since it must
        // wait for elements to be processed in order.
    }
}

6. forEach vs Traditional Loops

forEach() is not always the best choice. Understanding its limitations helps you pick the right iteration approach.

6.1 Comparison Table

Feature Traditional for Enhanced for-each forEach()
Index access Yes No No
Break / continue Yes Yes No
Checked exceptions Yes Yes No (must wrap)
Modify local variables Yes Yes No (must be effectively final)
Modify collection during iteration With care (by index) No (ConcurrentModificationException) No (ConcurrentModificationException)
Lambda / method reference No No Yes
Readability for simple actions Verbose Good Excellent
Return from enclosing method Yes Yes No (return only exits the lambda)

6.2 Key Limitations of forEach

import java.util.List;

public class ForEachLimitations {
    public static void main(String[] args) {
        List items = List.of("Apple", "Banana", "Cherry", "Date", "Elderberry");

        // --- LIMITATION 1: No break or continue ---
        // With traditional loop: stop at "Cherry"
        System.out.println("--- Traditional loop with break ---");
        for (String item : items) {
            if (item.equals("Cherry")) {
                break;
            }
            System.out.println(item);
        }
        // Output:
        // Apple
        // Banana

        // With forEach: return only exits the current lambda iteration, NOT the loop
        System.out.println("\n--- forEach with return (NOT a break!) ---");
        items.forEach(item -> {
            if (item.equals("Cherry")) {
                return;  // This skips only "Cherry", does NOT break the loop
            }
            System.out.println(item);
        });
        // Output:
        // Apple
        // Banana
        // Date
        // Elderberry

        // --- LIMITATION 2: Cannot modify local variables ---
        // This will NOT compile:
        // int count = 0;
        // items.forEach(item -> count++);  // Error: variable must be effectively final

        // Workaround (but use streams or traditional loop instead):
        int[] count = {0};  // Array is effectively final, its contents are not
        items.forEach(item -> count[0]++);
        System.out.println("\nCount: " + count[0]);
        // Output: Count: 5

        // --- LIMITATION 3: Cannot throw checked exceptions ---
        // This will NOT compile:
        // items.forEach(item -> {
        //     java.io.FileReader fr = new java.io.FileReader(item);  // throws IOException
        // });

        // Workaround: wrap in try-catch
        items.forEach(item -> {
            try {
                // Simulating a checked exception scenario
                if (item.equals("Date")) {
                    throw new Exception("Simulated error for " + item);
                }
            } catch (Exception e) {
                System.err.println("Error: " + e.getMessage());
            }
        });
    }
}

6.3 When to Use Each

Scenario Best Choice Why
Print each element forEach Simple, concise, readable
Need index Traditional for forEach does not provide index
Need to break early Traditional for or enhanced for-each forEach does not support break
Filter then act stream().filter().forEach() Chain operations before iterating
Checked exceptions in body Enhanced for-each forEach lambda cannot throw checked exceptions
Modify a local variable Traditional for Lambda requires effectively final variables
Simple side effect per element forEach Most concise and readable

7. Common Mistakes

7.1 Modifying the Collection During forEach

Modifying a collection while iterating over it with forEach() throws a ConcurrentModificationException.

import java.util.ArrayList;
import java.util.List;
import java.util.Iterator;

public class ForEachModifyMistake {
    public static void main(String[] args) {

        // --- WRONG: Modifying collection inside forEach ---
        List names = new ArrayList<>(List.of("Alice", "Bob", "Charlie", "Diana"));
        try {
            names.forEach(name -> {
                if (name.startsWith("C")) {
                    names.remove(name);  // ConcurrentModificationException!
                }
            });
        } catch (java.util.ConcurrentModificationException e) {
            System.out.println("Error: ConcurrentModificationException caught!");
        }
        // Output: Error: ConcurrentModificationException caught!

        // --- FIX 1: Use removeIf (best approach) ---
        List names2 = new ArrayList<>(List.of("Alice", "Bob", "Charlie", "Diana"));
        names2.removeIf(name -> name.startsWith("C"));
        System.out.println("After removeIf: " + names2);
        // Output: After removeIf: [Alice, Bob, Diana]

        // --- FIX 2: Use Iterator.remove() ---
        List names3 = new ArrayList<>(List.of("Alice", "Bob", "Charlie", "Diana"));
        Iterator it = names3.iterator();
        while (it.hasNext()) {
            if (it.next().startsWith("C")) {
                it.remove();  // Safe removal during iteration
            }
        }
        System.out.println("After Iterator.remove: " + names3);
        // Output: After Iterator.remove: [Alice, Bob, Diana]

        // --- FIX 3: Collect items to remove, then removeAll ---
        List names4 = new ArrayList<>(List.of("Alice", "Bob", "Charlie", "Diana"));
        List toRemove = new ArrayList<>();
        names4.forEach(name -> {
            if (name.startsWith("C")) {
                toRemove.add(name);
            }
        });
        names4.removeAll(toRemove);
        System.out.println("After removeAll: " + names4);
        // Output: After removeAll: [Alice, Bob, Diana]
    }
}

7.2 Unintended Side Effects

forEach() is designed for performing actions (side effects), but those side effects should be predictable and contained. Avoid complex state mutations inside forEach().

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class ForEachSideEffects {
    public static void main(String[] args) {
        List words = List.of("java", "python", "java", "go", "python", "java");

        // --- BAD: Complex state mutation inside forEach ---
        Map wordCount = new HashMap<>();
        words.forEach(word -> {
            if (wordCount.containsKey(word)) {
                wordCount.put(word, wordCount.get(word) + 1);
            } else {
                wordCount.put(word, 1);
            }
        });
        System.out.println("forEach approach: " + wordCount);
        // Output: forEach approach: {python=2, java=3, go=1}

        // --- BETTER: Use streams with Collectors for aggregation ---
        Map wordCountStream = words.stream()
            .collect(Collectors.groupingBy(w -> w, Collectors.counting()));
        System.out.println("Stream approach: " + wordCountStream);
        // Output: Stream approach: {python=2, java=3, go=1}

        // --- ALSO GOOD: Use Map.merge() ---
        Map wordCountMerge = new HashMap<>();
        words.forEach(word -> wordCountMerge.merge(word, 1, Integer::sum));
        System.out.println("Merge approach: " + wordCountMerge);
        // Output: Merge approach: {python=2, java=3, go=1}
    }
}

7.3 Thinking return Means break

Inside a forEach() lambda, return does not exit the loop. It only exits the current lambda invocation (equivalent to continue in a traditional loop). This is a frequent source of confusion.

import java.util.List;

public class ReturnVsBreak {
    public static void main(String[] args) {
        List numbers = List.of(1, 2, 3, 4, 5, 6, 7, 8);

        // --- Intention: stop processing after finding 5 ---

        // WRONG: return does not break the loop
        System.out.println("--- forEach with return (acts like continue, not break) ---");
        numbers.forEach(n -> {
            if (n > 5) {
                return;  // Only skips this lambda call, loop continues!
            }
            System.out.println("Processing: " + n);
        });
        // Output: Processing: 1, 2, 3, 4, 5 (but the loop still ran for 6, 7, 8)

        // CORRECT: Use a stream with takeWhile (Java 9+) for early termination
        System.out.println("\n--- stream takeWhile (Java 9+) ---");
        numbers.stream()
               .takeWhile(n -> n <= 5)
               .forEach(n -> System.out.println("Processing: " + n));
        // Output: Processing: 1, 2, 3, 4, 5

        // CORRECT: Use a traditional loop if you need break
        System.out.println("\n--- Traditional loop with break ---");
        for (int n : numbers) {
            if (n > 5) {
                break;
            }
            System.out.println("Processing: " + n);
        }
        // Output: Processing: 1, 2, 3, 4, 5
    }
}

8. Best Practices

# Practice Details
1 Use forEach for simple actions Printing, logging, sending notifications -- one-liner actions are perfect for forEach()
2 Use method references when possible list.forEach(System.out::println) is cleaner than list.forEach(x -> System.out.println(x))
3 Prefer streams for complex operations If you need to filter, map, or reduce, use the Stream API instead of forEach() with conditionals
4 Use traditional loops for control flow If you need break, continue, index access, or checked exceptions, use a traditional loop
5 Do not modify the source collection Use removeIf() or Iterator.remove() instead of removing inside forEach()
6 Use Map.forEach for map iteration map.forEach((k, v) -> ...) is cleaner than iterating over entrySet()
7 Use forEachOrdered with parallel streams If order matters and you are using parallel streams, use forEachOrdered()
8 Keep lambdas short If the lambda body exceeds 3-4 lines, extract it into a named method and use a method reference
import java.util.List;

public class ForEachBestPractices {
    public static void main(String[] args) {
        List emails = List.of(
            "alice@example.com",
            "invalid-email",
            "bob@company.org",
            "",
            "charlie@test.net"
        );

        // BAD: Complex logic crammed into forEach
        System.out.println("--- BAD: Complex forEach ---");
        emails.forEach(email -> {
            if (email != null && !email.isEmpty()) {
                if (email.contains("@")) {
                    String domain = email.substring(email.indexOf("@") + 1);
                    System.out.println("Valid: " + email + " (domain: " + domain + ")");
                } else {
                    System.out.println("Invalid: " + email);
                }
            }
        });

        // GOOD: Use streams for filtering, forEach for the final action
        System.out.println("\n--- GOOD: Stream pipeline + forEach ---");
        emails.stream()
              .filter(email -> email != null && !email.isEmpty())
              .filter(email -> email.contains("@"))
              .map(email -> email + " (domain: " + email.substring(email.indexOf("@") + 1) + ")")
              .forEach(result -> System.out.println("Valid: " + result));
        // Output:
        // Valid: alice@example.com (domain: example.com)
        // Valid: bob@company.org (domain: company.org)
        // Valid: charlie@test.net (domain: test.net)

        // GOOD: Extract complex logic into a method, use method reference
        System.out.println("\n--- GOOD: Method reference ---");
        emails.stream()
              .filter(ForEachBestPractices::isValidEmail)
              .forEach(ForEachBestPractices::processEmail);
        // Output:
        // Processing email: alice@example.com -> domain: example.com
        // Processing email: bob@company.org -> domain: company.org
        // Processing email: charlie@test.net -> domain: test.net
    }

    private static boolean isValidEmail(String email) {
        return email != null && !email.isEmpty() && email.contains("@");
    }

    private static void processEmail(String email) {
        String domain = email.substring(email.indexOf("@") + 1);
        System.out.println("Processing email: " + email + " -> domain: " + domain);
    }
}

9. Complete Practical Example

This example demonstrates forEach() across all collection types in a realistic scenario: processing student enrollment data.

import java.util.*;
import java.util.stream.Collectors;

public class ForEachPracticalExample {

    public static void main(String[] args) {
        System.out.println("========================================");
        System.out.println("  Student Enrollment Report Generator");
        System.out.println("========================================\n");

        // --- Setup: Student data ---
        Map> courseEnrollments = new LinkedHashMap<>();
        courseEnrollments.put("Java 101", List.of("Alice", "Bob", "Charlie", "Diana"));
        courseEnrollments.put("Python 201", List.of("Alice", "Eve", "Frank"));
        courseEnrollments.put("Data Structures", List.of("Bob", "Charlie", "Eve", "Grace"));
        courseEnrollments.put("Algorithms", List.of("Alice", "Diana", "Frank", "Grace"));

        Map studentGPA = new LinkedHashMap<>();
        studentGPA.put("Alice", 3.9);
        studentGPA.put("Bob", 3.2);
        studentGPA.put("Charlie", 3.5);
        studentGPA.put("Diana", 3.8);
        studentGPA.put("Eve", 3.1);
        studentGPA.put("Frank", 2.9);
        studentGPA.put("Grace", 3.7);

        // --- 1. List forEach: Print each course with student count ---
        System.out.println("--- Course Summary ---");
        courseEnrollments.forEach((course, students) ->
            System.out.printf("  %-20s : %d students%n", course, students.size()));
        // Output:
        //   Java 101             : 4 students
        //   Python 201           : 3 students
        //   Data Structures      : 4 students
        //   Algorithms           : 4 students

        // --- 2. Set forEach: Find all unique students ---
        Set allStudents = new TreeSet<>();
        courseEnrollments.values().forEach(students -> allStudents.addAll(students));

        System.out.println("\n--- All Enrolled Students (sorted) ---");
        allStudents.forEach(student -> System.out.println("  " + student));
        // Output (TreeSet = alphabetical order):
        //   Alice
        //   Bob
        //   Charlie
        //   Diana
        //   Eve
        //   Frank
        //   Grace

        // --- 3. Map forEach: Display GPA with honors status ---
        System.out.println("\n--- Student GPA Report ---");
        studentGPA.forEach((name, gpa) -> {
            String honors = gpa >= 3.5 ? " (Dean's List)" : "";
            System.out.printf("  %-10s : %.1f%s%n", name, gpa, honors);
        });
        // Output:
        //   Alice      : 3.9 (Dean's List)
        //   Bob        : 3.2
        //   Charlie    : 3.5 (Dean's List)
        //   Diana      : 3.8 (Dean's List)
        //   Eve        : 3.1
        //   Frank      : 2.9
        //   Grace      : 3.7 (Dean's List)

        // --- 4. Stream forEach: Find students in multiple courses ---
        System.out.println("\n--- Students in 2+ Courses ---");
        Map courseCount = new HashMap<>();
        courseEnrollments.values()
            .forEach(students -> students.forEach(student ->
                courseCount.merge(student, 1L, Long::sum)));

        courseCount.entrySet().stream()
            .filter(entry -> entry.getValue() >= 2)
            .sorted(Map.Entry.comparingByValue().reversed())
            .forEach(entry -> System.out.printf("  %-10s : %d courses%n",
                entry.getKey(), entry.getValue()));
        // Output:
        //   Alice      : 3 courses
        //   Bob        : 2 courses
        //   Charlie    : 2 courses
        //   Diana      : 2 courses
        //   Eve        : 2 courses
        //   Frank      : 2 courses
        //   Grace      : 2 courses

        // --- 5. forEach with conditional: Roster for each course ---
        System.out.println("\n--- Detailed Rosters ---");
        courseEnrollments.forEach((course, students) -> {
            System.out.println("  " + course + ":");
            students.forEach(student -> {
                double gpa = studentGPA.getOrDefault(student, 0.0);
                System.out.printf("    - %-10s (GPA: %.1f)%n", student, gpa);
            });
            System.out.println();
        });
        // Output:
        //   Java 101:
        //     - Alice      (GPA: 3.9)
        //     - Bob        (GPA: 3.2)
        //     - Charlie    (GPA: 3.5)
        //     - Diana      (GPA: 3.8)
        //
        //   Python 201:
        //     - Alice      (GPA: 3.9)
        //     - Eve        (GPA: 3.1)
        //     - Frank      (GPA: 2.9)
        //   ... etc.

        // --- 6. Summary statistics using forEach ---
        System.out.println("--- Summary Statistics ---");
        double[] totalGPA = {0};
        int[] count = {0};
        studentGPA.forEach((name, gpa) -> {
            totalGPA[0] += gpa;
            count[0]++;
        });
        System.out.printf("  Total students : %d%n", count[0]);
        System.out.printf("  Average GPA    : %.2f%n", totalGPA[0] / count[0]);
        System.out.printf("  Total courses  : %d%n", courseEnrollments.size());
        // Output:
        //   Total students : 7
        //   Average GPA    : 3.44
        //   Total courses  : 4

        // Better approach for statistics: use streams
        double avgGPA = studentGPA.values().stream()
            .mapToDouble(Double::doubleValue)
            .average()
            .orElse(0.0);
        System.out.printf("  Average GPA (stream) : %.2f%n", avgGPA);
        // Output: Average GPA (stream) : 3.44
    }
}

This example demonstrates forEach() in every context:

Usage Where Demonstrated
Map.forEach(BiConsumer) Course summary, GPA report, detailed rosters
Set.forEach(Consumer) Printing all unique students (TreeSet)
List.forEach(Consumer) Printing students within each course roster
stream().filter().forEach() Finding students enrolled in multiple courses
Nested forEach Course roster with per-student GPA lookup
forEach with merge() Counting course enrollments per student
Streams vs forEach for aggregation Average GPA calculation (both approaches compared)

10. Quick Reference

Method On Accepts Key Behavior
forEach(Consumer) Iterable (List, Set, Queue) Consumer<T> Iterates in collection order
forEach(BiConsumer) Map BiConsumer<K, V> Provides key and value
stream().forEach() Stream Consumer<T> Terminal op; no order guarantee with parallel
forEachOrdered() Stream Consumer<T> Guarantees encounter order even in parallel



Subscribe To Our Newsletter
You will receive our latest post and tutorial.
Thank you for subscribing!

required
required


Leave a Reply

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