Writing your own Iterable

Collections · lesson 38 of 42 · 4 min read

Make your own type work in a for-each loop by implementing Iterator correctly.

Open this lesson in the learning hub

Key points

  • A for-each loop works on anything that implements Iterable, which is a single method: iterator().
  • The compiler rewrites the loop into hasNext() and next() calls — there is no magic in it.
  • hasNext must consume nothing, and next must throw NoSuchElementException once exhausted.
  • Return a fresh iterator on every call, or a second loop over the same object finds nothing left.
  • For a read-only sequence leave remove() alone: the interface default already throws.
  • Extending AbstractList gives you iterator, contains and toString for free.

Example

import java.util.*;

public class Main {

    // Iterable is one method. This is everything for-each needs.
    static final class Range implements Iterable<Integer> {
        private final int from;
        private final int toExclusive;

        Range(int from, int toExclusive) {
            this.from = from;
            this.toExclusive = toExclusive;
        }

        @Override public Iterator<Integer> iterator() {
            return new Iterator<>() {                 // a fresh cursor per call
                private int cursor = from;
                @Override public boolean hasNext() { return cursor < toExclusive; }
                @Override public Integer next() {
                    if (!hasNext()) throw new NoSuchElementException("range exhausted");
                    return cursor++;
                }
            };
        }
    }

    // Two methods, and AbstractList supplies the rest of List.
    static final class Trio extends AbstractList<String> {
        private final String[] data = {"a", "b", "c"};
        @Override public String get(int i) { return data[i]; }
        @Override public int size() { return data.length; }
    }

    public static void main(String[] args) {
        StringBuilder sb = new StringBuilder();
        for (int i : new Range(1, 5)) sb.append(i).append(" ");
        System.out.println("for-each over Range : " + sb.toString().trim());

        Range r = new Range(1, 4);
        System.out.println("looped twice        : " + count(r) + " then " + count(r) + "   (fresh cursor each time)");

        Iterator<Integer> it = new Range(1, 2).iterator();
        it.next();
        try {
            it.next();
        } catch (NoSuchElementException e) {
            System.out.println("next() past the end : NoSuchElementException");
        }
        try {
            new Range(1, 3).iterator().remove();
        } catch (UnsupportedOperationException e) {
            System.out.println("inherited remove()  : UnsupportedOperationException");
        }

        Trio trio = new Trio();
        System.out.println("AbstractList gives  : " + trio + "  contains(b)=" + trio.contains("b")
                + "  indexOf(c)=" + trio.indexOf("c"));
    }

    static int count(Iterable<Integer> src) {
        int n = 0;
        for (int ignored : src) n++;
        return n;
    }
}

for-each is just hasNext and next. Implement those two honestly and your type joins the language.

This is a reading copy. The full lesson — with the visual explainer, the interactive lab and a Run button for the code — lives in the Collections course, and every lesson in it is listed on the Collections contents page.