Room 5 of 14 · about 30 minutes

The enhanced for loop, and when it fits

The counted loop always works. This room is the shorter loop, and the three conditions that have to hold before you may use it.

Tasks checked0 of 4 XP earned on this path0

What this room checks you can do

Student can produce an enhanced-for loop that visits every element of an array, choosing the correct element type for the loop variable, and identify the colon : as the keyword-equivalent that separates the variable from the array reference.

Notes

Enhanced-for ("for-each") syntax

The enhanced-for is a second loop form, designed for the read-every-element case. Its syntax is:

for (Type variable : collectionOrArray) {
    // body uses `variable` for the current element
}

The colon (:) is read aloud as "in": "for each Type variable in collectionOrArray". The loop visits every element in order, binding the variable to a fresh value each iteration. There is no index variable, no condition, no update: those three concerns are gone.

final int[] xs = {10, 20, 30};

for (int v : xs) {
    System.out.println(v);   //  prints 10, 20, 30
}

JLS §14.14.2 specifies the desugaring. The compiler rewrites the enhanced-for over an array into the equivalent traditional-for:

for (int #i = 0; #i < xs.length; #i++) {
    int v = xs[#i];
    System.out.println(v);
}

The hidden counter #i is created by the compiler and is not accessible in the body. That is what the enhanced-for is for: the value-at-each-slot case where the index is not needed.

When to use it

  • Reading every element to compute a sum, average, max, or count.
  • Printing every element.
  • Checking whether any/every element satisfies a condition.

When not to use it

  • The body needs the index: for a parallel-array access, for [i] formatting, for if (i == 0) boundary handling. Those three conditions determine which loop to use.
  • The body needs to mutate the slot. The loop variable is a copy of the element; assigning to it does not change xs. The rule that the loop variable is a copy covers this in detail.
  • The traversal must walk part of the array, walk in reverse, or skip elements.

In other languages

  • Python: for v in xs:: identical shape; the colon is part of the statement separator, not analogous to Java's. Python's enhanced-for is the only for loop; there is no C-style three-part form.
  • C++: for (int v : xs): range-based for, added in C++11. Same surface; same intent.
  • JavaScript: for (const v of xs): note of, not in. (for (k in obj) iterates keys, which is a separate construct.)
  • Go: for _, v := range xs { … }: the blank identifier _ discards the index when not needed.

When the enhanced-for fits

The enhanced-for is the right loop exactly when the body needs the current element's value and nothing else. Three things must be true:

1. You visit every element. The enhanced-for has no start, end, or step controls: it walks the whole array in order, beginning to end. Partial traversals, reverse traversals, and stride-by-two traversals all need a traditional for. 2. You do not need the index. No i in the body, no [i-1] look-behind, no parallel-array access like prices[i] paired with names[i]. If you find yourself writing int i = 0; for (int v : xs) { … i++; } to manufacture an index, you wanted a traditional for from the start. 3. You do not modify the array through the loop. The loop variable is a copy of the value (or a copy of the reference, for object arrays); writing back through it does not change the array. The rule that the loop variable is a copy covers this.

If any of the three is false, the traditional for is the right tool. There is no rule that says "always prefer enhanced-for": readability is the goal, and a traditional for reads more cleanly when its features (index, partial bounds, custom step) are actually used.

A worked decision

// Q: sum all elements
int sum = 0;
for (int v : xs) {                 //  enhanced-for: value-only, every slot
    sum += v;
}

// Q: print "index 0: 10", "index 1: 20", ...
for (int i = 0; i < xs.length; i++) { //  traditional: index needed for the label
    System.out.println("index " + i + ": " + xs[i]);
}

// Q: copy xs into ys, doubled
for (int i = 0; i < xs.length; i++) { //  traditional: writing to ys[i] needs an index
    ys[i] = xs[i] * 2;
}

// Q: check if any element is negative
boolean anyNeg = false;
for (int v : xs) {                 //  enhanced-for: value-only, no early break needed
    if (v < 0) { anyNeg = true; break; }
}

The break in the last example is legal in either loop form. Both break and continue work inside the enhanced-for.

Why the rule is useful

Choosing the right loop form is a code-review signal. A reviewer reading for (int v : xs) knows immediately that the body does not need the index, does not need to mutate xs, and visits every element. That information shortcuts mental simulation of the loop. A traditional for carries the opposite signal: the reviewer should look for the reason the index is needed. Mixing the two forms inconsistently destroys the signal.

In other languages

  • Python: for v in xs: is the default; the indexed form uses for i, v in enumerate(xs):. The convention is "use the enhanced-for unless you need the index."
  • C++: for (auto v : xs) (range-based) versus for (size_t i = 0; i < xs.size(); ++i). Same fit test.
  • Rust: for v in &xs versus for (i, v) in xs.iter().enumerate(). Rust's iter().enumerate() is the canonical "I need both" form.

The enhanced-for variable is a copy

In an enhanced-for over an array, each iteration assigns the current element's value to the loop variable. For primitives, that is a copy of the value. For object types, it is a copy of the reference (the object itself is shared). In neither case does writing to the loop variable change the array slot.

final int[] xs = {1, 2, 3};

for (int v : xs) {
    v = 99;                      //  modifies the local variable v
}
System.out.println(xs[0]);       //  prints 1: xs is unchanged

The compiler-generated desugaring (JLS §14.14.2) makes this concrete:

for (int #i = 0; #i < xs.length; #i++) {
    int v = xs[#i];              //  copy of xs[#i] into v
    v = 99;                      //  reassigns v; no path back to xs[#i]
}

After the assignment int v = xs[#i], the variable v and the slot xs[#i] are independent storage. Changes to v do not propagate.

Object arrays: the reference is copied, the object is shared

For an array of objects, the copy is of the reference. The object on the heap is still the same one, and mutating it through the reference does change what the array's slot points to (because both v and the slot point at the same object). What does not work is reassigning the slot through v:

final Point[] points = { new Point(1, 1), new Point(2, 2) };

for (Point p : points) {
    p.x = 99;                    //  WORKS: mutates the Point object that both p and points[i] reference
}
//  points[0].x == 99 now

for (Point p : points) {
    p = new Point(0, 0);         //  reassigns the local p; does NOT change points[i]
}
//  points[0] is still the same Point object as before

The distinction lines up with the rule that assignment copies a reference and not the object it points at. Java has no concept of an alias for an array slot; the for-each gives you the value (or the reference), not the slot itself.

How to write back to the array

Use the traditional for:

for (int i = 0; i < xs.length; i++) {
    xs[i] = xs[i] * 2;           //  write through the slot
}

There is no shorter form for in-place mutation in Java. (Streams' Arrays.setAll exists but is out of scope for CSCD 210.)

In other languages

  • Python: for v in xs: v = 99 has the same no-effect behavior: v is a local name; xs is a list of values that are still bound to their slots. Mutation requires for i in range(len(xs)): xs[i] = 99 or a list comprehension.
  • C++: range-based for (auto v : xs) copies; for (auto& v : xs) takes a reference and does allow write-back. C++ makes the choice explicit at the &; Java has no equivalent: the for-each is always by-copy.
  • Rust: for v in &xs is by-reference (immutable); for v in &mut xs is by-reference (mutable). Like C++, Rust makes the choice syntactically explicit.

What this room assumes you already have

Tasks

Do each one, then check the box. Checking a box is you saying you did it. You can uncheck a box if you check it by accident.

  1. trace
    Show the answer

    12.

  2. trace
    Show the answer

    enhanced-for.

  3. trace
    Show the answer

    1.

  4. trace
    Show the answer

    a, b, c on separate lines.

Self check

Type what you think the answer is. Getting it wrong costs nothing and you can try as many times as you want.

Given Point[] ps = { new Point(1, 0) }; for (Point p : ps) p.x = 99; System.out.println(ps[0].x); (where Point has public int x, y), predict the printed value.

Practice, untimed

Open this whenever you want, before the tasks or after them. Nothing in this section is recorded and nothing here is timed.

  1. Produce an enhanced-for that prints every element of double[] xs on its own line.write
    Show the answer

    for (double v : xs) System.out.println(v);.

  2. Produce an enhanced-for that counts how many elements of int[] xs are positive.write
    Show the answer

    int count = 0; for (int v : xs) if (v > 0) count++;.

  3. Identify what type the loop variable must be for a String[] array.write
    Show the answer

    String.

  4. Given a task "set every element of int[] xs to zero," recommend a loop form.trace
    Show the answer

    traditional-for (mutation through xs[i] = 0).

How this room finishes

This room is done when all 4 tasks are checked and the self check is answered.