Iterator (Iteratör)
Introduktion
Iterator är ett beteendemönster som låter dig traversera elementen i en samling sekventiellt utan att exponera dess underliggande representation — om det är en lista, ett träd, en graf eller något annat.
Problem
Du har en samling av objekt — kanske en anpassad trädstruktur för ett filsystem. Klientkoden behöver kunna loopa igenom alla noder, men den ska inte behöva känna till att det är ett träd. Och du vill kunna erbjuda flera olika traverseringsordningar: djupet-först, bredden-först, bara löv.
Att stoppa traverseringslogiken i samlingen bryter mot Single Responsibility Principle. Att exponera den interna strukturen bryter mot inkapsling.
Lösning
Iterator extraherar traverseringslogiken till ett eget objekt. Samlingen skapar en iterator via en fabriksmetod. Klientkoden arbetar mot iteratorn via ett standardgränssnitt — i Java är det Iterator<T> och Iterable<T>, vilket låter dig använda for-each direkt.
När ska Iterator användas?
- När du vill dölja den interna strukturen för en samling.
- När du vill erbjuda flera traverseringsstrategier för samma samling.
- När du vill ha ett enhetligt sätt att traversera olika typer av samlingar.
Struktur
| Roll | Ansvar |
|---|---|
| Iterator Interface | Deklarerar traverseringsoperationerna (hasNext, next). |
| Concrete Iterator | Implementerar traverseringslogiken för en specifik samling. |
| Iterable Interface | Deklarerar metod för att skapa en iterator. |
| Concrete Collection | Implementerar Iterable<T> och returnerar sin iterator. |
Exempel — Filsystemsträd (Java)
Scenariot: En trädstruktur för ett filsystem som kan traverseras djupet-först eller bredden-först — klientkoden ser bara for-each.
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Queue;
// ── Nodmodell ─────────────────────────────────────────────
public class FileNode {
private final String name;
private final boolean isFolder;
private final List<FileNode> children = new ArrayList<>();
public FileNode(String name, boolean isFolder) {
this.name = name;
this.isFolder = isFolder;
}
public FileNode add(FileNode child) {
children.add(child);
return this;
}
public String getName() { return name; }
public boolean isFolder() { return isFolder; }
public List<FileNode> getChildren() { return children; }
@Override
public String toString() {
return isFolder ? "📁 " + name : "📄 " + name;
}
}
// ── Djupet-först Iterator (DFS) ───────────────────────────
public class DepthFirstIterator implements Iterator<FileNode> {
private final Deque<FileNode> stack = new ArrayDeque<>();
public DepthFirstIterator(FileNode root) {
stack.push(root);
}
@Override
public boolean hasNext() {
return !stack.isEmpty();
}
@Override
public FileNode next() {
if (!hasNext()) throw new NoSuchElementException();
FileNode current = stack.pop();
// Lägg till barn i omvänd ordning så att första barnet processas först
List<FileNode> children = current.getChildren();
for (int i = children.size() - 1; i >= 0; i--) {
stack.push(children.get(i));
}
return current;
}
}
// ── Bredden-först Iterator (BFS) ──────────────────────────
public class BreadthFirstIterator implements Iterator<FileNode> {
private final Queue<FileNode> queue = new ArrayDeque<>();
public BreadthFirstIterator(FileNode root) {
queue.add(root);
}
@Override
public boolean hasNext() {
return !queue.isEmpty();
}
@Override
public FileNode next() {
if (!hasNext()) throw new NoSuchElementException();
FileNode current = queue.poll();
for (FileNode child : current.getChildren()) {
queue.add(child);
}
return current;
}
}
// ── Samling med fabriksmetoder ────────────────────────────
public class FileTree implements Iterable<FileNode> {
private final FileNode root;
public FileTree(FileNode root) {
this.root = root;
}
// Standard: djupet-först (används av for-each)
@Override
public Iterator<FileNode> iterator() {
return new DepthFirstIterator(root);
}
// Alternativ traversering
public Iterable<FileNode> breadthFirst() {
return () -> new BreadthFirstIterator(root);
}
}
// ── Klientkod ─────────────────────────────────────────────
public class Application {
public static void main(String[] args) {
FileNode root = new FileNode("projekt", true);
root.add(new FileNode("README.md", false))
.add(new FileNode("src", true)
.add(new FileNode("Main.java", false))
.add(new FileNode("models", true)
.add(new FileNode("User.java", false))
.add(new FileNode("Order.java", false))))
.add(new FileNode("tests", true)
.add(new FileNode("UnitTests.java", false)));
FileTree tree = new FileTree(root);
System.out.println("=== Djupet-först (DFS) ===");
for (FileNode node : tree) {
System.out.println(" " + node);
}
System.out.println("\n=== Bredden-först (BFS) ===");
for (FileNode node : tree.breadthFirst()) {
System.out.println(" " + node);
}
}
}
Output:
=== Djupet-först (DFS) ===
📁 projekt
📄 README.md
📁 src
📄 Main.java
📁 models
📄 User.java
📄 Order.java
📁 tests
📄 UnitTests.java
=== Bredden-först (BFS) ===
📁 projekt
📄 README.md
📁 src
📁 tests
📄 Main.java
📁 models
📄 UnitTests.java
📄 User.java
📄 Order.java
Tips:
Iterable<T>är ett funktionellt gränssnitt i Java —() -> new BreadthFirstIterator(root)är ett smidigt sätt att returnera en alternativ traversering utan att skapa en extra klass.
Fördelar
- Single Responsibility Principle — Traverseringslogiken är separerad från samlingen.
- Klientkoden kan använda for-each direkt tack vare
Iterable<T>. - Erbjud flera traverseringsstrategier utan att ändra samlingens klass.
Nackdelar
- Överkurs för enkla samlingar —
ArrayListochstream()täcker de flesta behov. - Kan vara ineffektivt om traverseringstillståndet är tungt att hålla i minnet.
Av Victor Hernandez från Bytebase.se