Loops
Java provides four loop forms: the C-style for, the enhanced for (Java 5, often called for-each), while, and do … while. The enhanced for is the conventional form for iterating over arrays and any type that implements Iterable<T>; the C-style for retains its place where the index is genuinely needed or where the iteration is non-trivial. The streams API (Java 8) provides a substantial alternative to explicit loops for many filter-map-reduce patterns; the choice between explicit loops and stream pipelines is one of the principal style decisions in modern Java.
while
The while loop tests its controlling expression before each iteration:
while (!queue.isEmpty()) {
var item = queue.poll();
process(item);
}
The construct is the appropriate one for iteration whose termination condition is checked at the start, and where the number of iterations is not known in advance. Reading from a stream, polling for a state change, and graph traversal are typical.
The controlling expression must be of type boolean (no implicit conversion from integer or reference):
while (input != null && hasMoreData(input)) {
/* ... */
}
do … while
The do … while loop tests its controlling expression after each iteration; the body always executes at least once:
int input;
do {
System.out.print("> ");
input = scanner.nextInt();
} while (input < 0 || input > 100);
The construct is appropriate for iterations whose first execution is unconditional. It is less common than while in idiomatic Java; the principal uses are input prompts and certain numerical methods.
The terminating semicolon after while (cond) is a syntactic peculiarity; it is the only compound statement in Java that requires one.
The C-style for
The C-style for combines initialisation, test, and step:
for (int i = 0; i < n; i++) {
process(data[i]);
}
The header has three parts:
- The init-statement (an expression statement or a declaration), evaluated once before the loop.
- The condition, evaluated before each iteration; the loop terminates when it is
false. - The iteration-expression, evaluated after each iteration.
Any of the three may be omitted; for (;;) is the conventional infinite loop:
for (;;) {
var event = queue.take();
if (event.isQuit()) break;
handle(event);
}
The init declaration scopes the variable to the loop:
for (int i = 0; i < n; i++) {
/* i is in scope */
}
// i is not in scope here
The C-style for is conventional in Java for index-based iteration (when the index matters) and for non-trivial step patterns. For “iterate over a collection” the conventional choice is the enhanced for.
Multi-variable for
The init-clause may declare multiple variables of the same type, and the iteration-expression may have multiple comma-separated expressions:
for (int i = 0, j = n - 1; i < j; i++, j--) {
swap(arr, i, j);
}
The form is occasionally useful for two-pointer or reverse-iteration patterns.
The enhanced for (for-each)
Java 5 introduced the enhanced for, the conventional loop for iterating over arrays and any type that implements Iterable<T>:
List<Integer> values = List.of(1, 2, 3, 4, 5);
for (int v : values) {
System.out.println(v);
}
int[] arr = { 1, 2, 3 };
for (int x : arr) {
System.out.println(x);
}
Map<String, Integer> ages = Map.of("alice", 30, "bob", 28);
for (var entry : ages.entrySet()) {
System.out.println(entry.getKey() + ": " + entry.getValue());
}
The element variable’s type may be specified explicitly (int v) or inferred via var (Java 10+):
for (var person : people) { /* person is Person */ }
for (var entry : map.entrySet()) { /* entry is Map.Entry<K, V> */ }
The iterable may be any expression that yields an Iterable<T>, an array, or (for the limited primitive types) a primitive array. Maps require explicit .entrySet(), .keySet(), or .values() because Map<K, V> does not directly implement Iterable.
Modification during iteration
A enhanced for over a List<T> may not modify the underlying list during the iteration; doing so throws ConcurrentModificationException on the next step:
for (Item item : items) {
if (item.isExpired()) {
items.remove(item); // throws ConcurrentModificationException
}
}
The conventional alternatives:
- Use
Iterator<T>.remove():
Iterator<Item> it = items.iterator();
while (it.hasNext()) {
Item item = it.next();
if (item.isExpired()) it.remove();
}
- Use
Collection.removeIf(Java 8+):
items.removeIf(Item::isExpired);
- Use a stream and collect to a new list:
items = items.stream().filter(item -> !item.isExpired()).collect(Collectors.toList());
The removeIf form is the conventional contemporary choice for “remove all elements matching a predicate”.
break and continue
break exits the innermost enclosing for, while, do, or switch:
for (Item item : items) {
if (item.matches(target)) {
found = item;
break;
}
}
continue skips the rest of the current iteration and proceeds to the next test:
for (Item item : items) {
if (item.isHidden()) continue;
process(item);
}
Java has labelled break and continue for nested loops:
outer:
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
if (matrix[i][j] == target) {
row = i; col = j;
break outer; // exits both loops
}
}
}
The label refers to the loop the statement should affect. break outer exits the labelled loop; continue outer skips to the next iteration of the labelled loop. The mechanism is rare in modern code; refactoring into a method whose return exits both is often clearer.
Iterators and Iterable
A type that wishes to support enhanced for implements Iterable<T>:
public interface Iterable<T> {
Iterator<T> iterator();
}
Iterator<T> declares three methods:
public interface Iterator<E> {
boolean hasNext();
E next();
default void remove() { throw new UnsupportedOperationException(); }
}
A user-defined iterable:
public class Range implements Iterable<Integer> {
private final int from, toExclusive;
public Range(int from, int toExclusive) {
this.from = from;
this.toExclusive = toExclusive;
}
@Override
public Iterator<Integer> iterator() {
return new Iterator<>() {
private int current = from;
@Override
public boolean hasNext() { return current < toExclusive; }
@Override
public Integer next() {
if (!hasNext()) throw new NoSuchElementException();
return current++;
}
};
}
}
for (int i : new Range(0, 5)) {
System.out.println(i); // 0, 1, 2, 3, 4
}
The mechanism is the conventional way to make a custom collection or sequence iterable. Modern Java often uses streams (Stream<T>) or the IntStream/LongStream/DoubleStream primitives for the same purpose.
forEach and method references
Every Iterable<T> admits the forEach default method (Java 8):
items.forEach(item -> process(item));
items.forEach(System.out::println);
The pattern is the conventional alternative when the body is short and side-effecting; for substantial bodies, the explicit for form is clearer.
Streams as an alternative
A substantial fraction of explicit loops in Java code can be replaced by stream pipelines:
// Explicit:
int total = 0;
for (Order o : orders) {
if (o.isActive()) total += o.amount();
}
// Stream:
int total = orders.stream()
.filter(Order::isActive)
.mapToInt(Order::amount)
.sum();
The full treatment is in Streams. The conventional contemporary advice:
- Use enhanced
forfor ordinary iteration where the body has side effects or is non-trivial. - Use streams for filter, map, reduce, group, and sort operations.
- Use the C-style
foronly when the index is genuinely needed or when iterating two collections in parallel. - Use iterators for custom sequences that should be lazy.
Common iteration patterns
Index and value together
Java does not provide a built-in enumerate (such as Python’s). The conventional substitutes:
// With index:
for (int i = 0; i < items.size(); i++) {
process(i, items.get(i));
}
// With stream and IntStream.range:
IntStream.range(0, items.size())
.forEach(i -> process(i, items.get(i)));
Iterating two collections in parallel
Iterator<A> ait = a.iterator();
Iterator<B> bit = b.iterator();
while (ait.hasNext() && bit.hasNext()) {
process(ait.next(), bit.next());
}
Or with streams:
IntStream.range(0, Math.min(a.size(), b.size()))
.forEach(i -> process(a.get(i), b.get(i)));
There is no built-in zip operation in the streams API; third-party libraries (Guava, jOOλ) provide one.
Reading lines from a file
try (var reader = Files.newBufferedReader(path)) {
String line;
while ((line = reader.readLine()) != null) {
process(line);
}
}
// Or with streams (Java 8+):
try (var lines = Files.lines(path)) {
lines.forEach(this::process);
}
Files.lines is the conventional contemporary choice; the returned Stream<String> is AutoCloseable and must be closed.
Iterating a map’s entries, keys, or values
for (var entry : map.entrySet()) {
System.out.println(entry.getKey() + ": " + entry.getValue());
}
for (var key : map.keySet()) { /* keys */ }
for (var val : map.values()) { /* values */ }
Reverse iteration
for (int i = items.size() - 1; i >= 0; i--) {
process(items.get(i));
}
// Or with Collections.reverse (mutates):
var reversed = new ArrayList<>(items);
Collections.reverse(reversed);
for (var item : reversed) { process(item); }
There is no for-each form for reverse iteration; the explicit index loop is the conventional answer.