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 super T> action) // On Map default void forEach(BiConsumer super K, ? super V> action)
It takes a Consumer (a functional interface that accepts one argument and returns nothing) and applies it to each element.
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
Lists are the most common collection type, and forEach() works naturally with them. Since List is ordered, forEach() processes elements in insertion order.
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]
}
}
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.
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
}
}
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
}
}
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.
| 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
}
}
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.
}
}
forEach() is not always the best choice. Understanding its limitations helps you pick the right iteration approach.
| 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) |
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());
}
});
}
}
| 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 |
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]
}
}
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}
}
}
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
}
}
| # | 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);
}
}
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) |
| 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 |