Iterator
Fornece uma forma de acessar sequencialmente os elementos de uma coleção sem expor sua representação interna — desacoplando o algoritmo de percorrimento da estrutura de dados subjacente.
Intenção
Encapsular o mecanismo de travessia de uma coleção num
objeto separado (o Iterator), expondo apenas uma interface de acesso
sequencial — temProximo() / proximo() no estilo
GoF, ou os protocolos nativos das linguagens (Symbol.iterator
e generators em TypeScript; Iterator/IteratorAggregate
e generators em PHP). O cliente percorre a coleção sem saber se ela é um
array, uma lista encadeada, uma árvore ou um resultado de banco de dados.
Catalogado pelo GoF (1994) como padrão comportamental, o Iterator é hoje
tão fundamental que todas as linguagens modernas o incorporaram nos seus
protocolos de iteração nativos. Em TypeScript, qualquer objeto que implemente
Symbol.iterator pode ser usado em for...of,
spread ([...col]) e desestruturação. Em PHP, implementar
Iterator ou IteratorAggregate habilita o
foreach nativo.
Problema
Uma aplicação de músicas armazena a playlist numa lista encadeada customizada. A interface de exibição precisa percorrer a lista para mostrar as faixas. A abordagem direta expõe a estrutura interna:
// Abordagem ingênua — NÃO faça isso:
class PlaylistUI {
mostrar(playlist: ListaEncadeada): void {
// Acoplamento direto à estrutura interna — acessa o nó diretamente.
let no = playlist.cabeca; // propriedade interna exposta
while (no !== null) {
console.log(no.valor); // acessa o campo interno do nó
no = no.proximo; // navega pela estrutura interna
}
}
}
Se a ListaEncadeada mudar para um array circular ou uma árvore
balanceada, toda a lógica de percorrimento na UI precisa ser reescrita.
Além disso, implementar uma segunda forma de travessia (ex.: em ordem
reversa) exige duplicar o loop em outro lugar — sem nenhuma abstração
reutilizável.
O Iterator extrai o percorrimento para um objeto dedicado. A coleção expõe
apenas um método criarIterador() — o cliente usa a interface do
Iterator sem saber nada sobre a estrutura interna da coleção.
Solução
O Iterator organiza o código em quatro participantes:
-
Iterator (interface): declara as operações de travessia.
No estilo GoF:
temProximo(): booleaneproximo(): T. Na SPL do PHP:current(),next(),rewind(),valid(),key(). Em TypeScript: o método[Symbol.iterator]()retornando um objeto comnext(): IteratorResult<T>. -
ConcreteIterator: mantém o cursor interno na coleção e
implementa as operações de travessia. Cada chamada a
criarIterador()retorna uma nova instância com seu próprio cursor — permitindo múltiplas travessias simultâneas independentes. -
Aggregate (interface): declara o método de criação do
iterador. Ex.:
criarIterador(): IteradorLista<T>. - ConcreteAggregate: a coleção concreta que implementa o Aggregate e retorna o ConcreteIterator adequado. Pode fornecer múltiplos tipos de iterador (frente, reverso, filtrado) sem alterar a interface da coleção.
Iterator nativo vs GoF
A estrutura GoF (com temProximo()/proximo() em
classes separadas) é útil para aprender o padrão e para linguagens sem
protocolos nativos de iteração. Em TypeScript moderno, o protocolo
Symbol.iterator com generators (function*) é mais
idiomático — ele cria automaticamente os estados de cursor via
yield, sem precisar de uma classe ConcreteIterator separada.
Em PHP, IteratorAggregate + Generator segue o mesmo princípio.
Estrutura
«interface»
IteradorLista<T>
┌──────────────────────────────┐
│ + temProximo(): boolean │
│ + proximo(): T │
└──────────────────────────────┘
▲
IteradorListaEncadeada<T>
┌──────────────────────────────┐
│ - atual: No<T> | null │
│ + temProximo(): boolean │
│ + proximo(): T │
└──────────────────────────────┘
▲ cria
│
«interface»
Agregado<T>
┌──────────────────────────────────────┐
│ + criarIterador(): IteradorLista<T> │
└──────────────────────────────────────┘
▲
ListaEncadeada<T> (ConcreteAggregate)
┌──────────────────────────────────────┐
│ - cabeca: No<T> | null │
│ + adicionar(valor: T): void │
│ + criarIterador(): IteradorLista<T> │
└──────────────────────────────────────┘
Fluxo de uso (estilo GoF):
const it = lista.criarIterador();
while (it.temProximo()) {
console.log(it.proximo()); // sem acessar cabeca, No nem proximo
}
Fluxo nativo (TypeScript):
for (const item of lista) { ... } // usa Symbol.iterator implicitamente
Exemplos de código
Exemplo 1 — Lista encadeada com Iterator manual (estilo GoF)
Implementação clássica do padrão: IteradorLista<T> como
interface, IteradorListaEncadeada<T> como ConcreteIterator
e ListaEncadeada<T> como Aggregate. O cliente percorre
a coleção sem nenhum acesso à estrutura interna de nós.
// ── Iterator interface (estilo GoF) ──────────────────────────
interface IteradorLista<T> {
temProximo(): boolean;
proximo(): T;
}
// ── Nó interno da lista ───────────────────────────────────────
// Classe privada do pacote — o cliente nunca a vê.
class No<T> {
proximo: No<T> | null = null;
constructor(readonly valor: T) {}
}
// ── ConcreteIterator ──────────────────────────────────────────
class IteradorListaEncadeada<T> implements IteradorLista<T> {
private atual: No<T> | null;
constructor(inicio: No<T> | null) {
this.atual = inicio;
}
temProximo(): boolean {
return this.atual !== null;
}
proximo(): T {
if (this.atual === null) {
throw new Error('Iterator: sem mais elementos');
}
const valor = this.atual.valor;
this.atual = this.atual.proximo;
return valor;
}
}
// ── ConcreteAggregate ─────────────────────────────────────────
class ListaEncadeada<T> {
private cabeca: No<T> | null = null;
private cauda: No<T> | null = null;
adicionar(valor: T): void {
const no = new No(valor);
if (this.cauda === null) {
this.cabeca = this.cauda = no;
} else {
this.cauda.proximo = no;
this.cauda = no;
}
}
// Cada chamada retorna um iterador com cursor próprio.
criarIterador(): IteradorLista<T> {
return new IteradorListaEncadeada(this.cabeca);
}
}
// ── Uso ──────────────────────────────────────────────────────
const lista = new ListaEncadeada<string>();
lista.adicionar('Maçã');
lista.adicionar('Banana');
lista.adicionar('Cereja');
const it = lista.criarIterador();
while (it.temProximo()) {
console.log(it.proximo());
}
// Maçã
// Banana
// Cereja
// Dois iteradores simultâneos na mesma lista — cursores independentes:
const it1 = lista.criarIterador();
const it2 = lista.criarIterador();
console.log(it1.proximo()); // Maçã
console.log(it2.proximo()); // Maçã (cursor independente)
console.log(it1.proximo()); // Banana
<?php
// PHP possui a interface nativa Iterator (SPL) com 5 métodos.
// Ao implementá-la, a classe pode ser usada diretamente no foreach.
// ── Nó interno ────────────────────────────────────────────────
class No
{
public ?No $proximo = null;
public function __construct(public readonly mixed $valor) {}
}
// ── ConcreteAggregate implementando Iterator (SPL) ────────────
// Aqui a lista e o iterador são a mesma classe — abordagem compacta.
// Para cursores independentes simultâneos, separe numa classe IteradorListaEncadeada.
class ListaEncadeada implements Iterator
{
private ?No $cabeca = null;
private ?No $cauda = null;
private ?No $cursor = null;
private int $posicao = 0;
public function adicionar(mixed $valor): void
{
$no = new No($valor);
if ($this->cauda === null) {
$this->cabeca = $this->cauda = $no;
} else {
$this->cauda->proximo = $no;
$this->cauda = $no;
}
}
// ── Métodos da interface Iterator (SPL) ──────────────────
public function rewind(): void
{
$this->cursor = $this->cabeca;
$this->posicao = 0;
}
public function valid(): bool
{
return $this->cursor !== null;
}
public function current(): mixed
{
return $this->cursor?->valor;
}
public function key(): int
{
return $this->posicao;
}
public function next(): void
{
$this->cursor = $this->cursor?->proximo;
$this->posicao++;
}
}
// ── Uso ──────────────────────────────────────────────────────
$lista = new ListaEncadeada();
$lista->adicionar('Maçã');
$lista->adicionar('Banana');
$lista->adicionar('Cereja');
// foreach nativo, graças à interface Iterator (SPL).
foreach ($lista as $indice => $fruta) {
echo "{$indice}: {$fruta}\n";
}
// 0: Maçã
// 1: Banana
// 2: Cereja
Exemplo 2 — Protocolo nativo: Symbol.iterator / Generator (TS) e IteratorAggregate / Generator (PHP)
As linguagens modernas incorporam o Iterator como protocolo nativo. Em
TypeScript, implementar [Symbol.iterator]() como um generator
(function* / método com yield) torna a coleção
compatível com for...of, spread e desestruturação — sem
precisar de uma classe ConcreteIterator separada. Em PHP,
IteratorAggregate + Generator alcança o mesmo resultado com
muito menos código do que os 5 métodos da interface Iterator.
// Lista encadeada com protocolo nativo Symbol.iterator via generator.
// Sem classes ConcreteIterator — o yield gerencia o cursor automaticamente.
class No<T> {
proximo: No<T> | null = null;
constructor(readonly valor: T) {}
}
class ListaEncadeada<T> {
private cabeca: No<T> | null = null;
private cauda: No<T> | null = null;
adicionar(valor: T): void {
const no = new No(valor);
if (!this.cauda) {
this.cabeca = this.cauda = no;
} else {
this.cauda.proximo = no;
this.cauda = no;
}
}
// Generator que implementa Symbol.iterator — torna a lista iterável nativamente.
*[Symbol.iterator](): Generator<T> {
let atual = this.cabeca;
while (atual !== null) {
yield atual.valor; // suspende aqui e entrega o valor ao chamador
atual = atual.proximo;
}
}
// Segundo generator: iterador filtrado (múltiplas estratégias de travessia).
*filtrar(predicado: (item: T) => boolean): Generator<T> {
for (const item of this) { // reutiliza Symbol.iterator
if (predicado(item)) yield item;
}
}
}
// ── Uso ──────────────────────────────────────────────────────
const lista = new ListaEncadeada<number>();
[3, 1, 4, 1, 5, 9, 2, 6].forEach(n => lista.adicionar(n));
// for...of usa Symbol.iterator implicitamente.
for (const n of lista) {
process.stdout.write(n + ' ');
}
// 3 1 4 1 5 9 2 6
console.log();
// Spread também usa Symbol.iterator.
const arr = [...lista];
console.log(arr); // [3, 1, 4, 1, 5, 9, 2, 6]
// Iterador filtrado: só valores ímpares.
for (const n of lista.filtrar(n => n % 2 !== 0)) {
process.stdout.write(n + ' ');
}
// 3 1 1 5 9
console.log();
// Múltiplas travessias independentes simultâneas são seguras:
// cada chamada ao generator cria um novo objeto com cursor próprio.
const itA = lista[Symbol.iterator]();
const itB = lista[Symbol.iterator]();
console.log(itA.next().value); // 3
console.log(itB.next().value); // 3 (cursor independente)
console.log(itA.next().value); // 1
<?php
// IteratorAggregate + Generator: implementação mais simples que a interface Iterator.
// getIterator() retorna um Generator — PHP trata automaticamente como Traversable.
class No
{
public ?No $proximo = null;
public function __construct(public readonly mixed $valor) {}
}
class ListaEncadeada implements IteratorAggregate
{
private ?No $cabeca = null;
private ?No $cauda = null;
public function adicionar(mixed $valor): void
{
$no = new No($valor);
if ($this->cauda === null) {
$this->cabeca = $this->cauda = $no;
} else {
$this->cauda->proximo = $no;
$this->cauda = $no;
}
}
// Generator que substitui os 5 métodos do Iterator com um único yield.
// Cada foreach cria uma nova instância do Generator com cursor próprio.
public function getIterator(): Generator
{
$atual = $this->cabeca;
while ($atual !== null) {
yield $atual->valor; // suspende; entrega o valor ao foreach
$atual = $atual->proximo;
}
}
// Generator filtrado: segunda estratégia de travessia.
public function filtrar(callable $predicado): Generator
{
foreach ($this as $item) { // reutiliza getIterator()
if ($predicado($item)) {
yield $item;
}
}
}
}
// ── Uso ──────────────────────────────────────────────────────
$lista = new ListaEncadeada();
foreach ([3, 1, 4, 1, 5, 9, 2, 6] as $n) {
$lista->adicionar($n);
}
// foreach nativo via IteratorAggregate.
foreach ($lista as $n) {
echo $n . ' ';
}
// 3 1 4 1 5 9 2 6
echo PHP_EOL;
// Conversão para array.
$arr = iterator_to_array($lista, false);
print_r($arr); // [3, 1, 4, 1, 5, 9, 2, 6]
// Iterador filtrado: só valores ímpares.
foreach ($lista->filtrar(fn($n) => $n % 2 !== 0) as $n) {
echo $n . ' ';
}
// 3 1 1 5 9
echo PHP_EOL;
// Travessias simultâneas independentes:
// cada foreach chama getIterator() e obtém um Generator novo.
$genA = $lista->getIterator();
$genB = $lista->getIterator();
$genA->current(); // 3
$genB->current(); // 3 (cursor independente)
$genA->next();
echo $genA->current() . "\n"; // 1
echo $genB->current() . "\n"; // 3 (genB ainda no primeiro)
Quando usar
- Quando você tem uma coleção customizada e não quer expor sua estrutura interna (nós, cursores, índices) para quem a percorre. O Iterator provê o acesso sequencial sem vazar detalhes de implementação.
- Quando precisar de múltiplas formas de travessia da mesma coleção — em frente, em reverso, em largura, em profundidade, filtrado. Cada estratégia se torna um Iterator separado retornado pela coleção.
-
Para tornar coleções customizadas compatíveis com loops nativos:
em TypeScript,
Symbol.iteratorhabilitafor...of, spread e desestruturação; em PHP,IteratorAggregatehabilitaforeach— sem nenhuma mudança na API pública da coleção. - Quando o código de percorrimento precisa ser reutilizado em múltiplos lugares sem duplicação — o Iterator encapsula o loop numa única definição.
Quando evitar
- Para arrays e coleções simples já nativamente iteráveis: o padrão adiciona complexidade sem benefício quando a coleção já é um array ou um tipo que o loop da linguagem já suporta nativamente.
-
Quando a coleção sempre será percorrida de um único modo:
se não há variação de estratégia de travessia e a estrutura é estável,
um método
paraArray()que retorna um array é mais simples e suficiente.
Prós e contras
Prós
- Desacopla o algoritmo de travessia da estrutura de dados — trocar de lista encadeada para árvore não quebra o cliente.
- Suporte a múltiplos iteradores simultâneos e independentes na mesma coleção, cada um com seu próprio cursor.
- Múltiplas estratégias de travessia (frente, reverso, filtrado) sem modificar a interface da coleção.
- Integração nativa com loops da linguagem via Symbol.iterator (TS) e Iterator/IteratorAggregate (PHP).
- Generators eliminam a classe ConcreteIterator separada, reduzindo drasticamente o código necessário.
Contras
- Overhead de criação de objeto iterador para coleções simples onde um índice seria suficiente.
- Modificar a coleção durante a iteração pode invalidar o cursor do iterador — comportamento indefinido que exige cuidado.
- Iteradores baseados em classes (estilo GoF) exigem mais código do que a abordagem funcional com generators.
Armadilhas comuns
1. Invalidar o iterador ao modificar a coleção durante a iteração
A armadilha mais perigosa: adicionar ou remover elementos da coleção
enquanto um iterador a percorre pode pular elementos, visitar o mesmo
elemento duas vezes ou causar exceções de acesso nulo. Em Java isso gera
ConcurrentModificationException; em TypeScript e PHP o
comportamento é indefinido e silencioso — o pior tipo de bug.
Regra de ouro: nunca modifique a coleção durante a iteração com o mesmo iterador. Se precisar filtrar e remover elementos, colete os índices ou itens a remover numa lista separada e modifique a coleção após o loop ter terminado.
2. Vazar a estrutura interna pelo Iterator
Um Iterator que expõe os nós internos (No<T>) em vez dos
valores (T) derrota o propósito do padrão. O cliente acaba
acessando it.proximo().valor e depois .proximo
diretamente — o encapsulamento foi quebrado. O Iterator deve expor somente
o tipo de dado que o cliente precisa, nunca os containers internos.
3. Estado compartilhado entre iteradores
Se uma coleção armazena o cursor interno como campo de instância (em vez
de retornar um objeto iterador separado com cursor próprio), dois loops
concorrentes "roubam" posições um do outro. A solução: cada chamada a
criarIterador() ou getIterator() deve retornar
um novo objeto com seu próprio cursor. Generators em TypeScript
e PHP resolvem isso automaticamente — cada invocação do generator cria
uma instância independente.
4. Esquecer o rewind em PHP
Ao usar a interface Iterator do PHP (não IteratorAggregate),
o foreach chama rewind() antes de iniciar o
loop. Se rewind() não reinicializa corretamente o cursor, o
segundo foreach na mesma instância começa do ponto onde o
primeiro parou — comportamento surpreendente. Com
IteratorAggregate + Generator isso não ocorre: cada
foreach chama getIterator() e obtém um Generator
novo com cursor zerado.
Padrões relacionados
O Iterator se integra com padrões estruturais que organizam coleções hierárquicas:
O Composite organiza objetos em estruturas de árvore (parte-todo); o Iterator é o complemento natural para percorrê-la. Um Iterator pode implementar travessia em largura (BFS) ou em profundidade (DFS) sobre a árvore de Composites — o cliente usa a mesma interface de iteração independentemente da estratégia de percorrimento escolhida. O Visitor é o par do Iterator em operações sobre estruturas: enquanto o Iterator percorre sequencialmente e entrega cada elemento ao chamador, o Visitor percorre a estrutura e aplica uma operação específica em cada tipo de nó — separando o algoritmo de processamento da estrutura do Composite.