JAVA / LOOPS AND ARRAYS
Enhanced for loops and read-only iteration
Iterate any array or Iterable with for (T x : items), and predict exactly which writes inside the body reach the original data and which are thrown away.
What you will learn
- Read every element of an array in order with for (int x : arr), no index to maintain
- Explain why assigning to the loop variable never changes the array element
- Mutate an object through the loop variable, but not replace the slot that holds it
- Switch to an indexed for when you need position, reverse order, or writes into slots
Understanding Enhanced for loops and read-only iteration
The enhanced for loop, written for (int score : scores), walks an array front to back without you ever naming an index. The compiler expands it into an ordinary indexed loop with a hidden counter and a hidden copy of the array reference, and at the top of every iteration it declares a fresh local variable and assigns one element into it. That expansion is the whole mental model: score is not the slot scores[i], it is a new variable that received a copy of whatever that slot held.
Because the loop variable is a separate variable, writing to it is a dead store. The statement score = 0; changes a copy that is about to go out of scope and leaves scores[i] exactly as it was, which is what read-only iteration means. The read-only part applies to the array's slots, not to whatever those slots point at: when the element type is a reference type, the copy points at the same object, so calling a mutating method on it, or writing row[0] when the element is an int[], is plainly visible after the loop. Replacing an element and mutating an element are different operations, and only the second one is reachable here.
What you give up is position. With no index you cannot go backwards, step by two, start at element one, compare an element with its neighbour, walk two arrays in lockstep, or store a computed result back into the slot you just read. Use the enhanced for when the body genuinely only needs to look at each element once in order, and use an indexed loop the moment you need any of those other things. The same syntax also drives anything implementing Iterable, where the compiler emits iterator(), hasNext() and next() calls instead of a counter.
import java.util.Arrays;
public class ReadOnlyIteration {
public static void main(String[] args) {
int[] scores = {70, 82, 91};
int total = 0;
for (int score : scores) {
total += score;
score = 0; // writes to the copy, never to scores[i]
}
System.out.println("total = " + total);
System.out.println("average = " + total / scores.length);
System.out.println("scores = " + Arrays.toString(scores));
}
}for (T x : items) copies each element into a fresh loop variable, so the body can read every element in order but can never write back into the array's slots.
Worked examples
Rows are references, ints are not
Shows that mutating through the loop variable is visible, while rebinding it is not.
public class RowReferences {
public static void main(String[] args) {
int[][] grid = { {1, 2}, {3, 4} };
for (int[] row : grid) {
row[0] = 0; // follows the reference into the real row
row = new int[] {9, 9}; // rebinds the local copy only
}
for (int[] row : grid) {
System.out.println(row[0] + " " + row[1]);
}
}
}Example explained
Line 1for (int[] row : grid) declares row as an int[], because each element of grid is a whole row.
Line 2row[0] = 0 follows the copied reference to the row object itself, so grid sees the change.
Line 3row = new int[] {9, 9} points the local variable elsewhere; grid[0] still holds the original row.
Line 4The second loop reads grid again and prints 0 2 and 0 4, showing which of the two writes survived.
Reading values versus writing slots
Demonstrates that trimming through the loop variable is lost and only an indexed assignment sticks.
public class SlotsVersusValues {
public static void main(String[] args) {
String[] names = {" ada ", " grace "};
for (String n : names) {
n = n.trim();
}
System.out.println("[" + names[0] + "]");
for (int i = 0; i < names.length; i++) {
names[i] = names[i].trim();
}
System.out.println("[" + names[0] + "]");
}
}Example explained
Line 1n = n.trim() builds a trimmed String and stores it in the loop variable, which dies at the end of the iteration.
Line 2Strings are immutable, so there is no mutating method that could reach the slot through n instead.
Line 3names[i] = names[i].trim() names the slot on the left of the assignment, which is the only way to replace an element.
Line 4The two printed lines differ, and that difference is exactly the limit of the enhanced for.
The array reference is captured once
Reassigning the array variable inside the body does not change what the loop is iterating.
public class CapturedOnce {
public static void main(String[] args) {
int[] data = {1, 2, 3};
for (int value : data) {
System.out.println("value " + value);
data = new int[] {7, 8, 9, 10};
}
System.out.println("data.length " + data.length);
}
}Example explained
Line 1The loop evaluates data once, before the first iteration, and holds that array in a hidden variable.
Line 2Assigning data inside the body only changes which array the name data refers to.
Line 3The body therefore runs three times, once per element of the original {1, 2, 3}.
Line 4After the loop, data refers to the four-element array, so data.length prints 4.
Important notes
A null array throws NullPointerException while the loop evaluates the array expression, before the body runs once; an empty array simply skips the body with no error.
The loop variable only needs to be assignment-compatible with the element type, so for (double d : intArray) compiles by widening and for (int i : integerArray) compiles by unboxing, though the latter throws NullPointerException on a null element.
Common mistakes
Writing for (int n : nums) n *= 2; to double an array. It compiles without a warning and runs, but nums is unchanged, so the bug surfaces later as data that never got updated.
Treating the loop variable as an index, as in for (int i : nums) System.out.println(nums[i]);. Here i holds a value, not a position, so you print the wrong elements or get ArrayIndexOutOfBoundsException as soon as a value exceeds nums.length - 1.
Declaring for (int row : grid) over an int[][]. That is a compile error, incompatible types: int[] cannot be converted to int, because each element of grid is an entire row.
Try it yourself
Change, predict, then run
Take int[] temps = {18, 21, 25, 19} and use a single enhanced for to print each value and count how many exceed 20, and inside that same loop also set the loop variable to 0. Print Arrays.toString(temps) afterwards and confirm the array is untouched.
Open the Java workspaceCheck your understanding
parts is a StringBuilder[] whose first element is a builder containing "a". The loop for (StringBuilder sb : parts) runs sb.append("!"); and then sb = new StringBuilder("x");. What does parts[0] contain after the loop?
- x
- a!
- a
- x!
Show answer
sb holds a copy of the reference, so append reaches the very object parts[0] points at and its content becomes a!. The reassignment looks like it should win because it happens last, but it only makes sb point somewhere else; nothing in an enhanced for writes a value back into parts[0], so x never reaches the array.