Topic 2.7
The Enhanced for Loop
In one line
for (Type item : collection) visits every element of an array or any Iterable (like a List) in order, with no index to get wrong. The loop variable is a copy of each element, and you must not add to or remove from a collection while walking it this way.
Think of it like this
A teacher handing back exam papers. She doesn't count 'paper number 0, paper number 1...'; she just takes the next paper from the pile until the pile is empty. The enhanced for loop works like that: 'for each paper in the pile, do this'. You get each item, but not its position number.
Words you'll meet
New words in this topic, in plain English. Come back here whenever one feels fuzzy.
- for-each loop
- Another name for the enhanced for loop: do something for each element.
- Element
- One item stored in an array or collection.
- Iterable
- A Java interface for anything that can hand out its elements one by one.
ListandSetare Iterable. - Iterator
- An object that walks through a collection:
hasNext()asks 'is there another?',next()gives it. - Reference
- The address of an object on the heap. Object variables hold references, not the objects themselves (Topic 3.11).
- ConcurrentModificationException
- The error an
ArrayList(and most collections) throws when it's changed while an iterator is walking it. - Desugaring
- The compiler rewriting a convenient piece of syntax into simpler code it already understands.
Step by step
01Same job, less to get wrong
Compare the two loops. The classic one has three places to make an off-by-one mistake (start, test, index). The for-each has none. When all you need is 'each element, in order', it's the safer choice.
int[] scores = {72, 95, 88};
for (int i = 0; i < scores.length; i++) { // classic
System.out.println(scores[i]);
}
for (int score : scores) { // enhanced
System.out.println(score);
}02Arrays, lists, sets and maps
Anything Iterable works: a List visits elements in order, a TreeSet in sorted order. A Map isn't Iterable itself; loop over one of its views. entrySet() gives you key and value together, which avoids a second lookup per key.
You'll meet these collections properly in Phase 9; for now, notice the loop looks the same for all of them.
List<String> fruits = List.of("apple", "banana", "cherry");
for (String f : fruits) {
System.out.println(f);
}
Map<String, Integer> stock = new TreeMap<>(Map.of("pens", 12, "ink", 3));
for (Map.Entry<String, Integer> e : stock.entrySet()) {
System.out.println(e.getKey() + " = " + e.getValue());
}
for (char c : "hey".toCharArray()) { // a String is not Iterable
System.out.print(c + "-");
}03The loop variable is a copy
Each iteration, Java copies the current element into the loop variable. Assigning a new value to that variable changes the copy only. The array is untouched.
To change the array's contents, you need the index: for (int i = 0; i < a.length; i++) a[i] *= 2;.
Many teams write for (final int x : a) (or turn on a lint rule) so that accidentally assigning to the copy becomes a compile error.
int[] a = {1, 2, 3};
for (int x : a) {
x = x * 2; // changes the copy only
}
System.out.println(Arrays.toString(a)); // [1, 2, 3]04Objects: the reference is copied, not the object
When the elements are objects, the loop variable gets a copy of the reference: it points at the same object as the array slot. Calling a method that changes the object (sb.append("!")) changes the shared object, so the array 'sees' it.
Reassigning the variable (sb = new StringBuilder()) only points the copy somewhere else; the array slot still points at the old object. This is exactly Java's pass-by-value rule, which Topic 3.3 explains in depth.
05What the compiler turns it into
For an array, javac stores the array in a hidden variable, reads its length once, and runs an index loop. That's why the loop over a null array fails with 'Cannot read the array length'.
For an Iterable, javac calls iterator() and loops on hasNext()/next(). A null list fails with 'Cannot invoke "java.util.List.iterator()"'.
// for (int s : scores) { body } becomes roughly:
int[] arr$ = scores;
int len$ = arr$.length; // NullPointerException here if scores is null
for (int i$ = 0; i$ < len$; i$++) {
int s = arr$[i$];
// body
}
// for (String name : names) { body } becomes roughly:
for (Iterator<String> it$ = names.iterator(); it$.hasNext(); ) {
String name = it$.next();
// body
}06Don't change a list while for-each walks it
An ArrayList keeps a counter, modCount, that goes up on every add or remove. Its iterator remembers the value when it starts. On each next(), if the counter has changed, it throws ConcurrentModificationException (fail-fast). The name is misleading: no threads are involved.
There's a nasty corner: removing the second-to-last element doesn't throw. After the removal, hasNext() compares the cursor with the new, smaller size, decides it's finished, and the loop quietly skips the last element. Fail-fast is a best-effort check, not a guarantee.
The safe ways: list.removeIf(x -> ...) (Java 8), or an explicit Iterator and it.remove(), which updates the iterator's own bookkeeping.
07var in for-each, and choosing the right loop
Since Java 10 you can write for (var name : names); the type is inferred from the element type (Topic 1.11). It helps with long types such as Map.Entry<String, List<Integer>>.
Pick for-each when you need each element once, in order, and don't need its position. Pick the classic for when you need the index, a different order or step, neighbours, or to modify array slots. Pick an explicit Iterator when you must remove while walking.
Try it yourself
- 1
Trigger the exception
In 'Removing safely', change
"Bob"to"Ana"in the first loop. Predict what happens, then run. You now getConcurrentModificationException, because the loop callsnext()again after the removal. - 2
Prove the copy
In 'The loop variable is a copy', add
finalto the first loop:for (final int x : numbers). Compile. The assignmentx = x * 10is now an error (cannot assign a value to final variable x), which is exactly the safety net teams want. - 3
Loop a map
In 'Lists, sets and maps', add
stock.put("glue", 7);and predict where it appears in the output. (ATreeMapkeeps keys sorted, sogluecomes first.)
Code & diagrams
Expected output
Total: 411
Average: 82.2
Best: 95
90 or above: 2
Vowels: 6Expected output
list: cherry
list: apple
list: banana
sorted set: apple
sorted set: banana
sorted set: cherry
ink = 3
paper = 500
pens = 12Expected output
After for-each: [1, 2, 3]
After index loop: [10, 20, 30]
Builders: [hi!, yo!]Expected output
visiting Ana
visiting Bob
Cy was never visited: [Ana, Cy]
Odds via iterator: [1, 3, 5]
Odds via removeIf: [1, 3, 5]Break it on purpose
Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.
Break #1
Remove from a list inside for-each
Remove an element from an ArrayList while a for-each loop walks it.
import java.util.ArrayList;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<String> names = new ArrayList<>(List.of("Ana", "Bob", "Cy", "Dee"));
for (String name : names) {
if (name.startsWith("A")) {
names.remove(name);
}
}
}
}Break #2
Loop over a null array
Set int[] scores = null; and loop over it.
public class Main {
public static void main(String[] args) {
int[] scores = null;
for (int s : scores) {
System.out.println(s);
}
}
}Myth vs fact
Myth
Assigning to the for-each variable updates the array.
Fact
The variable is a copy of the element. Use an index loop to change array slots.
Myth
The enhanced for loop is slower than an index loop.
Fact
Over arrays it compiles to an index loop. Over an ArrayList it uses an iterator, which the JIT optimises to about the same cost.
Myth
ConcurrentModificationException means two threads are involved.
Fact
It's usually one thread changing a collection while iterating over it.
Myth
If removing inside for-each didn't throw, the code is fine.
Fact
Removing the second-to-last element ends the loop early without an exception, silently skipping the last element.
Pro corner
Extra depth for experienced readers. New to this? Skip it for now and come back later.
- ▸
JLS 14.14.2 defines the translation precisely. For arrays the expression is evaluated once into a temporary, so reassigning the original array variable inside the loop doesn't affect the iteration. For
Iterable, the declared loop variable is assigned fromnext()with an implicit cast, which is where heap pollution from raw types surfaces asClassCastException. - ▸
Iterating a
LinkedListwith an index loop callsget(i), which walks from the nearest end each time: O(n²) in total. For-each uses the iterator: O(n). Over anArrayListboth are O(n). Use for-each when you don't know theListimplementation. - ▸
modCountchecking is best-effort: theArrayListiterator only checks innext()(andremove()), andhasNext()comparescursor != size. Concurrent collections (CopyOnWriteArrayList,ConcurrentHashMap) instead give weakly consistent iterators that never throw CME. - ▸
Escape analysis often removes the
Iteratorallocation entirely after inlining, so for-each over a collection usually creates no garbage in hot code.
Remember this
- 1
The syntax is
for (int score : scores), read aloud as 'for eachscoreinscores'. Java runs the body once per element, in order, putting the current element intoscoreeach time. It's also called the for-each loop and arrived in Java 5. - 2
It works on two kinds of things: arrays, and any object that implements
Iterable(everyList,SetandQueuefrom the collections framework, Phase 9). For aMap, loop overmap.entrySet(),keySet()orvalues(). AStringisn'tIterable; loop overstr.toCharArray()instead. - 3
There's no index, so it can't go out of bounds, but you also can't tell which position you're at, skip ahead, walk backwards, look at the neighbour
arr[i + 1], or walk two arrays side by side. For those, use the classicfor(Topic 2.6). - 4
The loop variable is a copy of the element. Assigning to it (
score = 0) changes the copy, not the array. For objects, the copy is a copy of the reference (Topic 3.3), so calling a method that changes the object (sb.append("!")) does affect the element, while reassigning the variable doesn't. - 5
You must not change a collection's structure (add or remove elements) while for-each is walking it.
ArrayListnotices and throwsConcurrentModificationException. UseIterator.remove(),removeIf(Java 8), or build a new list. - 6
The compiler rewrites the loop: over an array it becomes an index loop on a hidden copy of the array reference; over an
Iterableit becomesIterator it = list.iterator(); while (it.hasNext()) { T x = it.next(); ... }. Knowing this explains every rule above.
Explain it without notes
What can the enhanced for loop iterate over, and what does the compiler turn it into?
Why doesn't for (int x : arr) x = 0; clear the array, while for (StringBuilder sb : arr) sb.setLength(0); does clear every builder?
Why does removing from an ArrayList inside a for-each loop throw ConcurrentModificationException, and how should you remove instead?
When must you use a classic for loop instead of for-each?
Practice
Given String[] words = {"java", "loop", "enhanced", "for"}, use for-each to find the longest word and the total number of letters.
Count how many times the letter l appears in "hello world, hello loops" using for-each over the characters.
Given List<Integer> nums = List.of(3, 8, 1, 9, 4), use for-each to build a new ArrayList containing only the numbers greater than 3, then print it.
Trade-offs
- ↔
For-each removes index bugs and reads like English, but hides the position and can't modify slots or remove elements safely.
- ↔
For-each over a
Listworks for every implementation in O(n); an index loop is O(n) onArrayListbut O(n²) onLinkedList. - ↔
removeIfand streams (Phase 11) express filtering more directly than loops, but a plain for-each is easier to debug step by step.
Done when you can
Done when you can loop over arrays, lists, sets and map entries with for-each.
Done when you can explain why assigning to the loop variable doesn't change the array.
Done when you can write the desugared form of for-each over an array and over an Iterable.
Done when you can remove elements safely with
Iterator.remove()orremoveIf.Done when you can say when a classic for loop is required instead.