An iterator must report availability without consuming or advancing an element, return the next element from next(), and signal exhaustion with NoSuchElementException. A loop may hide violations because enhanced for uses only one access pattern. section 8.13 section 8.14
The loop that hides the defect
An iterator whose hasNext() increments its cursor and whose next() merely returns the cursor can produce the expected values in an enhanced for loop. The same iterator fails when Collections.min() calls next() before hasNext(), and it does not correctly throw NoSuchElementException at exhaustion. section 8.13
Implement custom iterators defensively:
- Keep availability checks observational: repeated hasNext() calls must not consume or skip elements. section 8.13
- Make next() work when called directly, returning the next element or throwing NoSuchElementException when none remains. section 8.13
- When determining availability requires reading ahead, cache the value, reuse it across hasNext() calls, and clear it only after next() returns it. section 8.13
- Prefer existing iterator-producing APIs such as IntStream.range(0, max).iterator() or reader.lines().iterator() when they fit. section 8.13
return new Iterator<>() {
String next;
public boolean hasNext() {
if (next != null) return true;
try {
next = reader.readLine();
} catch (IOException e) {
throw new UncheckedIOException(e);
}
return next != null;
}
public String next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
String cur = next;
next = null;
return cur;
}
};
Shows a corrected BufferedReader adapter that caches one line, tolerates repeated hasNext() calls, supports direct next(), and throws on exhaustion. section 8.13
