Implementing Iterable and Iterator: Custom For-Each Support in Java
The enhanced for-each loop works with any class that implements java.lang.Iterable. Implementing Iterable makes your custom data structures (trees, graphs, ranges, file line sequences) participatein for-each loops, Stream.of(), Collections utility methods, and any framework that accepts an Iterable. This article covers the Iterable and Iterator contracts, implementing them on a custom collection, lazy/infinite iterators, the fail-fast modification-check pattern, and the Spliterator interface for stream support.
The Iterable and Iterator Contracts
The Java enhanced for-each loop, for (T item : collection)java.lang.Iterable<T>Iterator<T> iterator()iterator()
Iterator<T>boolean hasNext()T next()void remove()UnsupportedOperationException
The contract is: next()hasNext()NoSuchElementExceptioniterator()Iterable
Implementing Iterable on a Custom Collection
As a concrete example, consider a Rangecurrent
import java.util.Iterator;
import java.util.NoSuchElementException;
public class Range implements Iterable<Integer> {
private final int start;
private final int end; // exclusive
public Range(int start, int end) {
if (start > end) throw new IllegalArgumentException(
"start must be <= end, got " + start + " and " + end);
this.start = start;
this.end = end;
}
@Override
public Iterator<Integer> iterator() {
// Return a fresh iterator each time — independent cursors
return new Iterator<>() {
private int current = start;
@Override
public boolean hasNext() {
return current < end;
}
@Override
public Integer next() {
if (!hasNext()) throw new NoSuchElementException();
return current++;
}
};
}
}
// Usage — works with for-each, streams, and collection utilities
Range r = new Range(1, 6);
for (int n : r) {
System.out.print(n + " "); // 1 2 3 4 5
}
System.out.println();
// Works with StreamSupport
import java.util.stream.StreamSupport;
int sum = StreamSupport.stream(r.spliterator(), false)
.mapToInt(Integer::intValue)
.sum();
System.out.println("Sum: " + sum); // 15
// Two independent iterators on the same Range
Iterator<Integer> it1 = r.iterator();
Iterator<Integer> it2 = r.iterator();
System.out.println(it1.next()); // 1
System.out.println(it2.next()); // 1 — independent from it1
Implementing a Fail-Fast Iterator
Java's standard collections use fail-fast iterators: if the underlying collection is structurally modified (elements added or removed) through any means other than the iterator's own remove()ConcurrentModificationException
The standard implementation technique is a modification counter: the collection maintains an int modCountmodCountexpectedModCountnext()modCount == expectedModCount
import java.util.*;
public class SimpleList<E> implements Iterable<E> {
private Object[] elements;
private int size;
private int modCount = 0; // incremented on every structural change
public SimpleList() { elements = new Object[10]; }
public void add(E e) {
if (size == elements.length) elements = Arrays.copyOf(elements, size * 2);
elements[size++] = e;
modCount++; // structural modification
}
public boolean remove(int index) {
Objects.checkIndex(index, size);
System.arraycopy(elements, index + 1, elements, index, size - index - 1);
elements[--size] = null;
modCount++; // structural modification
return true;
}
@Override
public Iterator<E> iterator() {
final int expectedMod = modCount; // snapshot at iterator creation time
return new Iterator<>() {
int cursor = 0;
@Override
public boolean hasNext() {
return cursor < size;
}
@Override
@SuppressWarnings("unchecked")
public E next() {
if (modCount != expectedMod)
throw new ConcurrentModificationException();
if (!hasNext()) throw new NoSuchElementException();
return (E) elements[cursor++];
}
};
}
}
SimpleList<String> list = new SimpleList<>();
list.add("a"); list.add("b"); list.add("c");
Iterator<String> it = list.iterator();
it.next(); // "a"
list.add("d"); // structural modification after iterator created
it.next(); // throws ConcurrentModificationException
modCountCopyOnWriteArrayListLazy and Infinite Iterators
One of the most useful applications of custom iterators is lazy generation, producing values on demand rather than precomputing them all. This is particularly valuable when the sequence is infinite (all Fibonacci numbers, all prime numbers) or when generating all values upfront would be prohibitively expensive (lines in a file, records from a database cursor).
A lazy iterator holds just enough state to compute the next value when asked. The consumer controls how many values are consumed: it can stop at any point without wasting memory or computation on values it never requested.
import java.math.BigInteger;
// Infinite Fibonacci sequence — only computes what the caller consumes
public class Fibonacci implements Iterable<BigInteger> {
@Override
public Iterator<BigInteger> iterator() {
return new Iterator<>() {
private BigInteger a = BigInteger.ZERO;
private BigInteger b = BigInteger.ONE;
@Override
public boolean hasNext() {
return true; // infinite sequence
}
@Override
public BigInteger next() {
BigInteger result = a;
BigInteger next = a.add(b);
a = b;
b = next;
return result;
}
};
}
}
// Consume only the first 10 Fibonacci numbers
Iterator<BigInteger> fib = new Fibonacci().iterator();
for (int i = 0; i < 10; i++) {
System.out.print(fib.next() + " ");
}
// 0 1 1 2 3 5 8 13 21 34
// With StreamSupport — take first N, then stop
long count = StreamSupport.stream(new Fibonacci().spliterator(), false)
.limit(20)
.filter(n -> n.mod(BigInteger.TWO).equals(BigInteger.ZERO))
.count();
System.out.println("Even Fibonacci numbers in first 20: " + count); // 7
Spliterator: Enabling Parallel Streams
The SpliteratorIteratorIteratorSpliteratorIterablespliterator()IteratorSpliterators.spliteratorUnknownSize()
For collections that know their size and structure, implementing SpliteratortrySplit()Spliteratornull
import java.util.Spliterator;
import java.util.function.Consumer;
// Spliterator for the Range class — enables parallel stream support
public class RangeSpliterator implements Spliterator<Integer> {
private int current;
private final int end;
public RangeSpliterator(int start, int end) {
this.current = start;
this.end = end;
}
@Override
public boolean tryAdvance(Consumer<? super Integer> action) {
if (current < end) {
action.accept(current++);
return true;
}
return false;
}
@Override
public Spliterator<Integer> trySplit() {
int remaining = end - current;
if (remaining < 2) return null; // too small to split
int mid = current + remaining / 2;
int splitStart = current;
current = mid; // this spliterator takes [mid, end)
return new RangeSpliterator(splitStart, mid); // new one takes [start, mid)
}
@Override public long estimateSize() { return end - current; }
@Override public int characteristics() {
return ORDERED | SIZED | SUBSIZED | IMMUTABLE | DISTINCT | SORTED;
}
}
// Use it
Spliterator<Integer> sp = new RangeSpliterator(0, 1_000_000);
long sum = StreamSupport.stream(sp, true) // true = parallel
.mapToLong(Integer::longValue)
.sum();
System.out.println(sum); // 499999500000
characteristics()SpliteratorSIZEDSORTEDDISTINCT