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

RollAnsvar
Iterator InterfaceDeklarerar traverseringsoperationerna (hasNext, next).
Concrete IteratorImplementerar traverseringslogiken för en specifik samling.
Iterable InterfaceDeklarerar metod för att skapa en iterator.
Concrete CollectionImplementerar 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 — ArrayList och stream() 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