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:
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:
// 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:
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+):
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
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:
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:
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
// 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
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
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
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
// 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! 🚀