Iteratoren und Generatoren in Java: Eine ausführliche Anleitung
What is it and why? (real-life analogy)
Imagine you are a librarian, and in front of you stands a huge bookshelf with books. You need to write down the titles of all books published in 2023. You have several ways to do this:
- The "greedy" way: You take all books off the shelf, carry them to the table, sort through them, write down the ones you need, and then carry them back. If there are 10,000 books, the table will be cluttered, you'll get tired of carrying them, and if there are a million books, the table will simply break.
- The "lazy" way: You take a notebook and pen, walk up to the shelf, look at the first book. If it fits — you write it down. Move to the second one. And so on. You don't carry books around, you just go through them one by one.
In programming, an iterator is exactly that "lazy librarian". It is a special object that can iterate over elements of a collection (list, set, array) one by one, without loading the entire collection into RAM.
And a generator is an even cooler thing. It's like you're not just going through ready-made books, but writing their titles on the fly. For example, you generate an infinite list of Fibonacci numbers. You don't store them all in memory — you output the next number only when it's requested.
Important: Java does not have a built-inyieldkeyword like Python or C#. Generators in Java are implemented through theIteratorandIterableinterfaces, but with tricky "lazy" computation logic.
Why is this needed?
- Memory savings: You can process a 100 GB file by reading it one line at a time, rather than loading it entirely.
- Unified interface: It doesn't matter what's inside — an array, list, tree, or network stream. The iterator provides a unified way to iterate: "give me the next element".
- Lazy evaluation: You can create an infinite sequence (e.g., all prime numbers) and take exactly as many as you need from it.
Theory: how it works internally
Let's look "under the hood" of Java and understand how iterators are structured at the interface level.
The Iterator interface (generic)
This is the main "engine" of iteration. It is located in the java.util package and contains three methods:
| Method | Description |
|---|---|
boolean hasNext() |
Returns true if there are more elements in the collection to iterate over. If there are no more elements — false. |
T next() |
Returns the next element of the collection. If there are no more elements — throws a NoSuchElementException. |
void remove() |
Removes the last returned element from the collection. Rarely used, as it can lead to errors. |
How does this work in a loop?
When you write for (String s : list), the Java compiler invisibly transforms this code into the following:
// What you write:
for (String s : list) {
System.out.println(s);
}
// What it turns into (simplified):
Iterator<String> it = list.iterator(); // Get the iterator
while (it.hasNext()) { // While there is a next element
String s = it.next(); // Get it
System.out.println(s); // Do something with it
}
The Iterable interface
For an object to be usable in a for-each loop, it must implement the Iterable interface. It has only one method:
public interface Iterable<T> {
Iterator<T> iterator(); // Returns an iterator for this object
}
All standard Java collections (ArrayList, HashSet, LinkedList, etc.) implement this interface. Therefore, they can be iterated over in a loop.
What is the "internal state" of an iterator?
Each iterator stores its current position in the collection. For example, for an ArrayList, it's simply a number — the index of the current element. When you call next(), the index increases by 1. When the index equals the size of the list — hasNext() returns false.
For more complex structures (e.g., a binary tree), the iterator might store an entire stack of nodes to "remember" where to go next.
Important note: An iterator is a single-use object. You cannot "rewind" it backwards. If you need to traverse the collection again — you need to request a new iterator via collection.iterator().
Generators: lazy sequence
Java does not have a yield keyword, but we can create our own "generator" — a class that implements Iterator and computes the next element only at the moment next() is called.
Imagine a conveyor belt: raw materials (data) go in, finished parts come out. A generator is a machine that makes one part at a time, only when it's ordered. It doesn't make all the parts in advance and doesn't stockpile them.
Syntax in Java: all forms and variants
Let's look at all the ways to work with iterators in Java — from the classic to modern innovations.
1. Classic way (Java 1.2+)
The oldest and most explicit way. You manually get the iterator and call its methods.
import java.util.*;
public class ClassicIteratorExample {
public static void main(String[] args) {
Lis