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 RoadThe 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
OlegThat 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 overforEach 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 3Common 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 failedAdd implements Iterable<String> and an iterator() method that returns members.iterator().