Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

Your own Iterable

The for-each loop works not only with the standard collections. It accepts an array or any object whose class implements the Iterable interface. That means your own class can learn to work in for-each too.

The Iterable interface

The Iterable<T> interface has one required method — iterator(). It returns an iterator: an object that hands out elements one at a time. If the class keeps its elements in a list, it is enough to return that list's iterator:

class Playlist implements Iterable<String> {
    private final List<String> songs = new ArrayList<>();
 
    void add(String song) {
        songs.add(song);
    }
 
    public Iterator<String> iterator() {
        return songs.iterator();
    }
}
 
void main() {
    Playlist playlist = new Playlist();
    playlist.add("Morning");
    playlist.add("The Road");
 
    for (String song : playlist) {
        IO.println(song);
    }
}

Output:

Morning
The Road

The songs list is not visible from outside, yet the playlist can be iterated — encapsulation stays intact.

Your own iterator

Sometimes the elements are not stored anywhere but computed on the fly. Then you write the iterator yourself. The Iterator<T> interface has two methods: hasNext() says whether there are more elements, and next() returns the next one:

record Range(int from, int to) implements Iterable<Integer> {
    public Iterator<Integer> iterator() {
        return new Iterator<>() {
            private int current = from;
 
            public boolean hasNext() {
                return current <= to;
            }
 
            public Integer next() {
                return current++;
            }
        };
    }
}
 
void main() {
    for (int n : new Range(3, 6)) {
        IO.print(n + " ");
    }
}

Output:

3 4 5 6 

The iterator here is an anonymous class. It keeps its position in the current field, so every new loop starts from the beginning.

What for-each does

The for-each loop is a shorthand. The compiler turns it into this loop with an iterator:

void main() {
    List<String> names = List.of("Anna", "Oleg");
    Iterator<String> it = names.iterator();
    while (it.hasNext()) {
        String name = it.next();
        IO.println(name);
    }
}

Output:

Anna
Oleg

That is why for-each works with any Iterable: all it needs is hasNext() and next().

Rules for next()

When the elements run out, next() must throw a NoSuchElementException — every Java iterator behaves this way. Let's add the check to Range:

record Range(int from, int to) implements Iterable<Integer> {
    public Iterator<Integer> iterator() {
        return new Iterator<>() {
            private int current = from;
 
            public boolean hasNext() {
                return current <= to;
            }
 
            public Integer next() {
                if (!hasNext()) {
                    throw new NoSuchElementException("The range is over");
                }
                return current++;
            }
        };
    }
}
 
void main() {
    Iterator<Integer> it = new Range(1, 1).iterator();
    IO.println(it.next());
    IO.println(it.next());
}

Output:

1
Exception in thread "main" java.util.NoSuchElementException: The range is over

forEach for free

Once a class implements Iterable, it gets the forEach() method for free — it is already written in the interface itself:

record Range(int from, int to) implements Iterable<Integer> {
    public Iterator<Integer> iterator() {
        return new Iterator<>() {
            private int current = from;
 
            public boolean hasNext() {
                return current <= to;
            }
 
            public Integer next() {
                return current++;
            }
        };
    }
}
 
void main() {
    new Range(1, 3).forEach(n -> IO.println("Step " + n));
}

Output:

Step 1
Step 2
Step 3

Common mistake

If a class holds a list but does not implement Iterable, its objects cannot be used in for-each — the compiler does not know where to get the elements from:

class Team {
    List<String> members = List.of("Anna", "Oleg");
}
 
void main() {
    for (String name : new Team()) {
        IO.println(name);
    }
}

Output:

Main.java:6: error: for-each not applicable to expression type
    for (String name : new Team()) {
                       ^
  required: array or java.lang.Iterable
  found:    Main.Team
1 error
error: compilation failed

Add implements Iterable<String> and an iterator() method that returns members.iterator().

Thanks for reading!

Donate to the site — we work hard for you.