JAVA Learning

Hub de Conteúdo

Iterator

Percorra Collections com Segurança e Eficiência - Padrão de Design para Iteração

1. O que é Iterator?

Iterator é um padrão de design que permite percorrer elementos de uma coleção sem expor sua estrutura interna. É uma forma segura e eficiente de acessar elementos um a um.

Um Iterator funciona como um navegador que sabe como mover-se por qualquer tipo de coleção (List, Set, Queue) sem se preocupar com sua implementação.

É melhor do que usar índices porque funciona com qualquer coleção, mesmo aquelas que não têm índices (como Set).

2. Conceitos Fundamentais

Iteração

Processo de acessar cada elemento de uma coleção um de cada vez, na sequência desejada.

Padrão de Design

Solution reutilizável para problemas comuns em programação. Iterator é um dos padrões mais fundamentais.

Encapsulamento

O Iterator esconde os detalhes de como a coleção está organizada internamente, exposing apenas uma interface simples.

Segurança

O Iterator detecta modificações na coleção durante a iteração e lança exceção se apropriado.

3. Interface Iterator

A interface Iterator define os métodos básicos para qualquer iterador:

Métodos Principais:

  • hasNext() - Retorna true se há próximo elemento
  • next() - Retorna o próximo elemento
  • remove() - Remove o último elemento retornado (opcional)

Assinatura:

java
public interface Iterator<E> {
    // Retorna true se há próximo elemento
    boolean hasNext();
    
    // Retorna o próximo elemento e avança o cursor
    E next();
    
    // Remove o último elemento retornado por next()
    default void remove() {
        throw new UnsupportedOperationException("remove");
    }
    
    // Java 8+ - Executa ação para cada elemento restante
    default void forEachRemaining(Consumer<? super E> action) {
        Objects.requireNonNull(action);
        while (hasNext())
            action.accept(next());
    }
}

4. Usando Iterator

Padrão Básico:

java
// Criar uma coleção
List<String> frutas = new ArrayList<>();
frutas.add("Maçã");
frutas.add("Banana");
frutas.add("Laranja");
frutas.add("Uva");

// Obter o Iterator
Iterator<String> iterator = frutas.iterator();

// Iterar sobre elementos
while (iterator.hasNext()) {
    String fruta = iterator.next();
    System.out.println(fruta);
}

// Saída:
// Maçã
// Banana
// Laranja
// Uva

Removendo Elementos Durante Iteração:

java
List<Integer> numeros = new ArrayList<>();
numeros.add(1);
numeros.add(2);
numeros.add(3);
numeros.add(4);
numeros.add(5);

// Remover números pares
Iterator<Integer> it = numeros.iterator();
while (it.hasNext()) {
    Integer num = it.next();
    if (num % 2 == 0) {
        it.remove(); // Remove durante iteração (seguro)
    }
}

System.out.println(numeros); // [1, 3, 5]

Usando forEachRemaining() (Java 8+):

java
List<String> nomes = new ArrayList<>();
nomes.add("João");
nomes.add("Maria");
nomes.add("Pedro");

Iterator<String> it = nomes.iterator();

// forEachRemaining - executa ação para cada elemento
it.forEachRemaining(nome -> System.out.println(nome));

// Saída:
// João
// Maria
// Pedro

5. Exemplo Completo: Sistema de Tarefas

java
import java.util.*;

public class Tarefa {
    private String descricao;
    private boolean concluida;
    
    public Tarefa(String descricao) {
        this.descricao = descricao;
        this.concluida = false;
    }
    
    public void concluir() {
        this.concluida = true;
    }
    
    public boolean estaConcluida() {
        return concluida;
    }
    
    public String getDescricao() {
        return descricao;
    }
    
    @Override
    public String toString() {
        String status = concluida ? "✓ CONCLUÍDA" : "○ PENDENTE";
        return status + " - " + descricao;
    }
}

public class GerenciadorTarefas {
    private List<Tarefa> tarefas;
    
    public GerenciadorTarefas() {
        this.tarefas = new ArrayList<>();
    }
    
    public void adicionarTarefa(Tarefa tarefa) {
        tarefas.add(tarefa);
        System.out.println("Tarefa adicionada: " + tarefa.getDescricao());
    }
    
    // Exibir todas as tarefas
    public void exibirTodas() {
        System.out.println("\n=== TODAS AS TAREFAS ===");
        Iterator<Tarefa> it = tarefas.iterator();
        if (!it.hasNext()) {
            System.out.println("Nenhuma tarefa!");
            return;
        }
        while (it.hasNext()) {
            System.out.println(it.next());
        }
    }
    
    // Exibir apenas tarefas pendentes
    public void exibirPendentes() {
        System.out.println("\n=== TAREFAS PENDENTES ===");
        Iterator<Tarefa> it = tarefas.iterator();
        int contador = 0;
        while (it.hasNext()) {
            Tarefa t = it.next();
            if (!t.estaConcluida()) {
                System.out.println(t);
                contador++;
            }
        }
        if (contador == 0) {
            System.out.println("Nenhuma tarefa pendente!");
        }
    }
    
    // Exibir apenas tarefas concluídas
    public void exibirConcluidas() {
        System.out.println("\n=== TAREFAS CONCLUÍDAS ===");
        Iterator<Tarefa> it = tarefas.iterator();
        int contador = 0;
        while (it.hasNext()) {
            Tarefa t = it.next();
            if (t.estaConcluida()) {
                System.out.println(t);
                contador++;
            }
        }
        if (contador == 0) {
            System.out.println("Nenhuma tarefa concluída!");
        }
    }
    
    // Remover tarefas concluídas
    public void limparConcluidas() {
        Iterator<Tarefa> it = tarefas.iterator();
        int removidas = 0;
        while (it.hasNext()) {
            Tarefa t = it.next();
            if (t.estaConcluida()) {
                it.remove();
                removidas++;
            }
        }
        System.out.println("\n" + removidas + " tarefa(s) removida(s)!");
    }
    
    // Contar tarefas
    public int contar() {
        return tarefas.size();
    }
}

// Uso
public class Principal {
    public static void main(String[] args) {
        GerenciadorTarefas gerenciador = new GerenciadorTarefas();
        
        // Adicionar tarefas
        gerenciador.adicionarTarefa(new Tarefa("Estudar Java"));
        gerenciador.adicionarTarefa(new Tarefa("Fazer exercício"));
        gerenciador.adicionarTarefa(new Tarefa("Revisar conceitos"));
        gerenciador.adicionarTarefa(new Tarefa("Dormir cedo"));
        
        // Exibir todas
        gerenciador.exibirTodas();
        
        // Concluir algumas
        Tarefa t1 = gerenciador.tarefas.get(0);
        t1.concluir();
        
        Tarefa t2 = gerenciador.tarefas.get(1);
        t2.concluir();
        
        // Exibir por categoria
        gerenciador.exibirConcluidas();
        gerenciador.exibirPendentes();
        
        // Limpar concluídas
        gerenciador.limparConcluidas();
        
        // Total
        System.out.println("\nTotal de tarefas: " + gerenciador.contar());
        gerenciador.exibirTodas();
    }
}

6. Iterator vs For-Each vs For Tradicional

Três Formas de Iterar:

java
List<String> nomes = Arrays.asList("Ana", "Bruno", "Carlos");

// 1. FOR TRADICIONAL - Usa índice
System.out.println("=== FOR TRADICIONAL ===");
for (int i = 0; i < nomes.size(); i++) {
    System.out.println(nomes.get(i));
}

// 2. FOR-EACH - Mais limpo (internamente usa Iterator)
System.out.println("\n=== FOR-EACH ===");
for (String nome : nomes) {
    System.out.println(nome);
}

// 3. ITERATOR - Controle total, permite remoção segura
System.out.println("\n=== ITERATOR ===");
Iterator<String> it = nomes.iterator();
while (it.hasNext()) {
    System.out.println(it.next());
}

// 4. STREAM (Java 8+) - Programação funcional
System.out.println("\n=== STREAM ===");
nomes.stream().forEach(System.out::println);

Comparação:

Aspecto For Tradicional For-Each Iterator Stream
Sintaxe Verbosa Limpa Moderada Funcional
Acesso ao Índice ✓ Sim ✗ Não ✗ Não ✗ Não
Remover Durante Iteração ✗ Arriscado ✗ Não ✓ Seguro ✗ Não
Funciona com Set ✗ Não ✓ Sim ✓ Sim ✓ Sim
Suporte a Filter ✗ Não ✗ Não ✗ Não ✓ Sim
Quebra/Continua ✓ Sim ✓ Sim ✓ Sim ✗ Não

Quando Usar Cada Um:

For Tradicional:

  • Precisa do índice
  • Quer controle total do loop
  • Percorre apenas alguns elementos

For-Each:

  • Percorre todos os elementos
  • Não precisa do índice
  • Quer código mais limpo

Iterator:

  • Precisa remover elementos
  • Quer controle fino sobre iteração
  • Trabalha com qualquer coleção

Stream (Java 8+):

  • Quer programação funcional
  • Precisa filtrar/mapear dados
  • Trabalha com grandes volumes

7. ListIterator - Iterador Bidirecional

ListIterator é uma versão mais poderosa do Iterator que permite iterar em ambas as direções (para frente e para trás).

Métodos Adicionais:

  • hasPrevious() - Há elemento anterior?
  • previous() - Retorna elemento anterior
  • nextIndex() - Índice do próximo elemento
  • previousIndex() - Índice do elemento anterior
  • add(E) - Adiciona elemento na posição atual
  • set(E) - Substitui último elemento acessado

Exemplo Prático:

java
List<String> nomes = new ArrayList<>();
nomes.add("Alice");
nomes.add("Bob");
nomes.add("Carlos");
nomes.add("Diana");

// Obter ListIterator
ListIterator<String> lit = nomes.listIterator();

// Iterar para frente
System.out.println("=== PARA FRENTE ===");
while (lit.hasNext()) {
    String nome = lit.next();
    System.out.println(nome);
}

// Iterar para trás
System.out.println("\n=== PARA TRÁS ===");
while (lit.hasPrevious()) {
    String nome = lit.previous();
    System.out.println(nome);
}

// Iterador a partir de índice específico
System.out.println("\n=== A PARTIR DO ÍNDICE 2 ===");
ListIterator<String> lit2 = nomes.listIterator(2);
while (lit2.hasNext()) {
    System.out.println(lit2.next() + " (índice: " + lit2.previousIndex() + ")");
}

// Adicionar e substituir
System.out.println("\n=== ADICIONAR E SUBSTITUIR ===");
ListIterator<String> lit3 = nomes.listIterator();
while (lit3.hasNext()) {
    String nome = lit3.next();
    if (nome.equals("Bob")) {
        lit3.set("Robert"); // Substitui Bob
        lit3.add("Bonnie");  // Adiciona depois
    }
}

System.out.println("Lista modificada: " + nomes);
// [Alice, Robert, Bonnie, Carlos, Diana]

8. Boas Práticas com Iterator

✓ FAÇA:

  • Use while (iterator.hasNext()) antes de chamar next()
  • Use iterator.remove() para remover elementos seguramente
  • Prefira for-each quando não precisa remover elementos
  • Use ListIterator para iteração bidirecional
  • Documente quando usa Iterator para operações específicas
  • Use Iterator em métodos que recebem Collection como parâmetro

✗ EVITE:

  • Chamar next() sem verificar hasNext()
  • Remover elementos da coleção durante iteração sem usar Iterator
  • Modificar a coleção durante iteração de outras formas
  • Usar Iterator quando for-each é mais apropriado
  • Misturar remoções via Iterator e direto na coleção
  • Ignorar ConcurrentModificationException

9. Erros Comuns com Iterator

❌ Erro 1: NoSuchElementException

java
// ERRADO - Sem verificar hasNext()
List<String> lista = new ArrayList<>();
lista.add("A");

Iterator<String> it = lista.iterator();
it.next(); // OK
it.next(); // NoSuchElementException!

// CORRETO - Sempre verificar
Iterator<String> it = lista.iterator();
while (it.hasNext()) {
    System.out.println(it.next());
}

❌ Erro 2: ConcurrentModificationException

java
List<Integer> numeros = new ArrayList<>();
numeros.add(1);
numeros.add(2);
numeros.add(3);

// ERRADO - Remover durante iteração normal
Iterator<Integer> it = numeros.iterator();
while (it.hasNext()) {
    Integer num = it.next();
    if (num == 2) {
        numeros.remove(num); // ConcurrentModificationException!
    }
}

// CORRETO - Usar iterator.remove()
Iterator<Integer> it = numeros.iterator();
while (it.hasNext()) {
    Integer num = it.next();
    if (num == 2) {
        it.remove(); // Seguro!
    }
}

❌ Erro 3: Chamar remove() Incorretamente

java
List<String> cores = new ArrayList<>();
cores.add("Vermelho");
cores.add("Verde");

Iterator<String> it = cores.iterator();

// ERRADO - remove() sem ter chamado next()
// it.remove(); // IllegalStateException!

// ERRADO - remove() duas vezes
it.next();
it.remove();
// it.remove(); // IllegalStateException!

// CORRETO - remove() após next()
it.next();
it.remove(); // OK
it.next();
it.remove(); // OK

❌ Erro 4: Usar Iterator com Set Incorretamente

java
Set<String> conjunto = new HashSet<>();
conjunto.add("A");
conjunto.add("B");

// ERRADO - Tentar acessar por índice (Set não tem índice)
// String primeiro = conjunto.get(0); // Erro!

// CORRETO - Usar Iterator
Iterator<String> it = conjunto.iterator();
while (it.hasNext()) {
    System.out.println(it.next());
}

// OU com for-each
for (String item : conjunto) {
    System.out.println(item);
}

10. Quando Usar Iterator

Ideal para Iterator:

Remoção Segura de Elementos

Quando precisa remover elementos durante iteração

Iterator<Tarefa> it = tarefas.iterator();
while (it.hasNext()) {
    if (it.next().estaConcluida()) {
        it.remove(); // Seguro!
    }
}

Ideal para Iterator:

Trabalhar com Qualquer Collection

Iterator funciona com List, Set, Queue

public void processar(Collection<String> items) {
    Iterator<String> it = items.iterator();
    while (it.hasNext()) {
        System.out.println(it.next());
    }
}

Ideal para For-Each:

Iteração Simples

Quando apenas precisa ler elementos

for (String item : items) {
    System.out.println(item);
}

Ideal para Stream:

Transformação de Dados

Filtrar, mapear ou processar funcionalmente

items.stream()
    .filter(item -> item.length() > 5)
    .map(String::toUpperCase)
    .forEach(System.out::println);

11. Implementar Seu Próprio Iterator

java
// Classe que implementa Iterable
public class MinhaLista<E> implements Iterable<E> {
    private E[] elementos;
    private int tamanho;
    
    @SuppressWarnings("unchecked")
    public MinhaLista(int capacidade) {
        elementos = new Object[capacidade];
        tamanho = 0;
    }
    
    public void adicionar(E elemento) {
        if (tamanho < elementos.length) {
            elementos[tamanho++] = elemento;
        }
    }
    
    @Override
    public Iterator<E> iterator() {
        return new MeuIterador();
    }
    
    // Classe interna que implementa Iterator
    private class MeuIterador implements Iterator<E> {
        private int posicao = 0;
        
        @Override
        public boolean hasNext() {
            return posicao < tamanho;
        }
        
        @SuppressWarnings("unchecked")
        @Override
        public E next() {
            if (!hasNext()) {
                throw new NoSuchElementException();
            }
            return (E) elementos[posicao++];
        }
        
        @Override
        public void remove() {
            throw new UnsupportedOperationException(
                "Remoção não suportada neste iterator");
        }
    }
}

// Uso
public class Teste {
    public static void main(String[] args) {
        MinhaLista<String> lista = new MinhaLista<>(5);
        lista.adicionar("A");
        lista.adicionar("B");
        lista.adicionar("C");
        
        // Usar com for-each (funciona porque implementa Iterable)
        for (String item : lista) {
            System.out.println(item);
        }
        
        // Usar explicitamente Iterator
        Iterator<String> it = lista.iterator();
        while (it.hasNext()) {
            System.out.println(it.next());
        }
    }
}

12. Exercícios Práticos

Exercício 1: Filtrar e Remover

Crie uma lista de números e use Iterator para remover todos os números menores que 10.

Exercício 2: Bidirecionário com ListIterator

Use ListIterator para iterar uma lista de palavras para frente, depois para trás.

Exercício 3: Remover Duplicatas

Use Iterator para remover elementos duplicados de um ArrayList.

Exercício 4: Implementar Iterable

Crie uma classe personalizada que implemente Iterable e seu próprio Iterator.

Exercício 5: Comparar Performance

Compare performance entre For Tradicional, For-Each, Iterator e Stream em uma lista grande.

13. Resumo - Iterator em Contexto

Por que Iterator é Importante?

  • Padrão de Design - Solution reutilizável e provada
  • Uniformidade - Mesma interface para List, Set, Queue
  • Segurança - Remoção segura durante iteração
  • Flexibilidade - Implementar próprios iteradores
  • Performance - Otimizado para cada tipo de coleção

Hierarquia de Iteração em Java:

  • Iterator - Básico, unidirecional
  • ListIterator - Bidirecional, com operações adicionais
  • For-Each - Sintaxe de alto nível (usa Iterator internamente)
  • Stream - Programação funcional (Java 8+)

Próximos Passos:

  • Estude ListIterator para operações mais avançadas
  • Explore Stream API para processamento funcional
  • Aprenda sobre padrões de design como Iterator é implementado
  • Pratique criando seus próprios iteradores personalizados

Conclusão

Iterator é um padrão fundamental em Java que permite iterar sobre qualquer coleção de forma segura e uniforme.

Compreender Iterator é essencial para trabalhar com Collections eficientemente, especialmente quando precisa remover elementos durante iteração.

Domine Iterator, ListIterator, for-each e Streams para ter total controle sobre processamento de coleções em Java! 🚀