Iterator

Iterator(Iterador) é um padrão de projeto que visa simplificar a iteração sobre um conjunto de objetos[1].

O problema

A iteração é uma forma especial de repetição em que executamos alguma lógica sobre um elemento de um conjunto. Para tipos primitivos como inteiros a iteração é um processo inerente suportado pela própria linguagem de programação e em ultima análise ao próprio computador já que nada é mais que uma soma (ou subtração).

1
2 for ( int i = 0 ; i < MAXIMO ; i++ ){
3 // trabalha com i
4 }

Código 1:

Se temos uma agregação de objetos, normalmente utilizaríamos um array para poder acessar cada elemento.

1
2 Object [] array = new Object [ 10 ]
3 for ( int i = 0 ; i < array.length ; i++ ){
4 Object obj = array [ i ] ;
5 // trabalha com obj
6 }

Código 2:

Utilizando array como forma de agrupar os elementos a iteração é simples e semelhante à de primitivos. ( na realidades ainda estamos iterado sobre o inteiro i) Mas e quando os elementos não estão agrupados em um array?

A solução

O padrão propõe que se utilize uma interface para controlar a iteração. O objeto que implementar esta interface conhece a seqüência do grupo e mantém uma referencia ao elemento atual de forma que possa ser utilizado de forma semelhante à iteração em um array. Por exemplo:

1
2 ObjectGroup groupOfObjects = new ObjectGroup () ;
3 Iterator it = Iterator.iteratorFor ( groupOfObjects ) ;
4
5 while ( it.hasMore () ){
6 Object obj = it.current () ;
7 // trabalha com obj
8 }

Código 3:

Como funciona

A interface Iterator é simples. Um método para pegar objeto corrente e um método para sabermos se chegámos ao fim da seqüência. Contudo a implementação de um iterador tem algumas nuances importantes.

Conjuntos Vazios

Se o nosso iterador nos retorna a posição corrente, ele tem que obter o elemento corrente assim que é criado. Se estivermos iterando sobre um conjunto vazio é impossível obter o primeiro elemento do conjunto. Dessa forma o elemento corrente terá que ser null. Isto representa vários problemas práticos.
O primeiro é que retornar null força a constante verificação se o elemento retornado é nulo. É uma forma muito chata de usar um iterador pois seriamos obrigados a escrever sempre algo como :

01
02
03 Iterator it = …
04
05 while ( it.hasMore () ){
06 Object obj = it.current () ;
07 if ( obj != null ){
08 // trabalha com obj
09 }
10 }

Código 4:

Mas a verificação só faz sentido para o primeiro elemento. O segundo problema é que o iterador não deveria sequer iterar sobre um conjunto vazio. Isto é fácil de resolver. Basta que hasMore retorna false se o conjunto é vazio. Mas hasMore implica que houve pelo menos um elemento.

Não é apenas uma questão de nomes para os métodos. O iterador tem que ter regras que o impeçam de iterar conjuntos vazios e tornar isso transparente para quem o utiliza. Java tem suporte nativo a este padrão de projeto na interface java.util.Iterator e resolve este problema.

java.util.Iterator

Java inclui a interface java.util.Iterator como parte do Java Collections Framework, um conjunto de classes especializadas em representar e manipular conjuntos de elementos.

1
2 public interface Iterador<T> {
3
4 T next () ;
5 boolean hasNext () ;
6 }

Código 5:

O método hasNext() verifica se existe um elemento depois do atual. O método next() retorna esse valor. É importante notar que o iterador que cumpre esta interface não se posiciona em nenhuma elemento quando é criado. Qualquer java.util.Iterator começa com nenhum elemento corrente. O método hasNext() teste se o elemento seguinte existe, no caso, ele verifica se o conjunto a iterar não é vazio. O método next() é responsável por proseguir para o elemento seguinte e o retornar.

O problema com padrão iterator é que ele retorna um objeto de uma coleção, mas a coleção pode ser de qualquer coisa. Antes do Java 5 a interface retorna um Object obrigando ao uso constante de cast. No Java 5 com a introdução dos tipos genéricos a interface passou a “saber” que tipo de objeto retornar. Evitando cast e aumentando a força da restrição de tipos.

Use a interface provida pelo java sempre que possível ( ou seja: 99,99% das vezes) mas quando construir o seu iterador tente ao máximo torná-lo fortemente tipado. Isso poupa, não só o uso de muitos cast,mas também aumenta a segurança e a legibilidade do seu código.

Removendo enquanto iteramos

Na realidade a interface da interface java.util.Iterator apresentada antes está incompleta. Existe mais um método remove().

1
2 public interface Iterador<T> {
3
4 T next () ;
5 boolean hasNext () ;
6 remove () ;
7 }

Código 6:

Na prática, por questões de eficiência, enquanto um iterador associado a uma coleção está iterando os elementos, a coleção não pode ser modificada. Isso implica que não podemos remover o elemento corrente, nem nenhum outro. Contudo, é também um necessidade prática poder fazer isso, caso contrário teriamos que constantemente copiar elementos para outra coleção. Para colmatar o problema a interface java.util.Iterator conta com o método remove() que permite remover o elemento corrente da coleção. A sua implementação é opcional já que não está ligado diretamente ao conceito do padrão Iterator, mas deve ser implementando sempre que possível. Quando não, o método deve retornar uma exceção UnsupportedOperationException.

Obtendo o iterador

No exemplo do inicio construímos o iterador explicitamente. Na prática o objecto que implementa a interface depende dos elementos no conjunto, dependendo portanto do próprio conjunto. Não ha nada que nos impeça de criarmos um iterador genérico apenas acontece que se o fizermos, teremos que nos lembrar de todas as formas possíveis de conjunto. É simplesmente mais prático que o conjunto nos entregue um iterador pronto para uso. A vantagem é que o iterador pode fazer uso de conhecimentos da estrutura interna do conjunto para ser mais eficiente.

Assim, cada conjunto que pode ser iterador usando um iterador deve ter um método que nos permita ter acesso a ele. Em Java esse método é normalmente chamado de iterator() e é uma especialização do padrão de projeto Factory Method No Java 5.0 foi adicionada uma nova interface que explicita quando um objeto fornece um iterador : Iterable

Obter o iterador para uma qualquer das coleções presentes em Java é simples. Basta utilizar o método iterator().

1
2 for ( Iterator it = list.iterator () ; it.hasNext () ; ){
3 Object obj = it.next () ;
4 }

Código 7:

Se está perguntando porque mudei o while para for, a razão é ajudar o coletor de lixo (Garbage Collector). O iterador é um objeto utilitário. Ele cumpre uma tarefa especifica, no fim da qual, ele é dispensável. Utilizando o for e inicializando o iterador dentro do seu escopo garantimos que o iterador estará disponivel para recolha logo assim que for terminar.

Implementado Iterator

Normalmente você não terá que criar um iterador quando utilizar as coleções providas pelo java. Mas a utilidade deste padrão vai muito além das classes providas pelo Java. Para exemplificar a construção de iterador vamos criar um iterador sobre arrays.

Como arrays são objetos especiais em java, não poderemos criar o método iterator() para elas. Termos que construir o iterador explicitamente.

01
02
03 public class ArrayIterator<T> implements Iterator<T> {
04
05 T [] array;
06 int current = – 1 ; // começa antes do primeiro elemento
07 public ArrayIterator ( T [] array ){
08 this .array = array;
09 }
10
11 public boolean hasNext (){
12 return current < array.length- 1 ;
13 }
14
15 public T next (){
16 current++;
17 if ( current < 0 || current > array.length- 1 ){
18 // o indice está além do limite do indices disponiveis no array
19 throw new ArrayindexOutOfBoundsException () ;
20 }
21 return array [ current ] ;
22 }
23
24 public void remove (){
25 // é impossivel editar o array através deste iterador
26 throw new UnsupportedOperationException () ;
27 }
28 }

Código 8:

Note que colocamos uma verificação se o índice é válido. Isto é importante porque o método next() pode ser chamado fora da uma iteração ou mais do que uma vez na iteração, o que provocaria um erro ao acessar ao array. Aplicando a boa prática de “Não deixe para os outros o que você pode lançar primeiro” é correto fazer o teste antes de chamar o elemento do array.

Relação com outros padrões

O Iterator se relaciona principalmente com o padrão Factory Method quando a própria classe de coleção sabe como , e qual, iterador criar.

Referências

[1] Design Patterns: Elements of Reusable Object-Oriented Software
Gamma et al.
URL:

Licença

Creative Commons License Sérgio Taborda
Este trabalho é licenciado sob a
Licença Creative Commons Atribuição-Uso Não-Comercial-Não a obras derivadas 3.0 Genérica .

12 opiniões sobre “Iterator”

  1. não entendi. Implementei a interface iterator, não obstante não encontrei o método interatorFor, pois ela só possui três métodos: hasNext, next e remove.

    att.

    1. ele apenas criou o seu próprio Iterator, é o mesmo que se espera da API nativa do java, como ele não importou java.util.Iterator; ele pode usar essa interface com o mesmo nome, já que está no pacote corrente. esse método ele usa como bem quer (e renomeia tbm) mas o comportamento precisa ser o mesmo do Iterator da API.

  2. Não entendi de onde tirou a ideia do método “iteratorFor”. Como pode ver no Código 5 acima, e como vc já descobriu, a interface não tem esse método.

  3. Boa noite,
    estou com alguns problemas com o método Interator,
    não consigo imprimir o conteúdo da minha fila de pedidos.
    se puder ajudar agradeço.
    grato,
    David

    public Pedido elementosfila()
    {
    Iterator percorre = NovoPedido.iterator();

    // Está imprimino o Endereco e não os atributos (Pedidos mesmo)

    JOptionPane.showMessageDialog(null,"Pedidos na Fila: "+NovoPedido);

    while (percorre.hasNext())
    //Enquanto não chegar ao ultimo elemento
    {

    JOptionPane.showMessageDialog(null,"O Pedido da Vez é: "+(Pedido) percorre.next());

    percorre.remove();

    }

    1. Seu codigo está meio complexo mas O iterator deveria ser em cima da fila de pedidos seria

      Iterator percorre = filaDePedidos.iterator(); e não NovoPedidoIterator.

      use um for e não um while

      porquê está dando remove ? isso retira os elementos de onde eles estão.

      Se quer o ultimo elemento vc precia memorizá-lo assim


      Iterator it = fila.iterator();
      Pedido utltimo = null;
      while (it.hasNext()){
      ultimo = it.next();
      }

      return ultimo;

      Não use JOptionPane para debug, use System.out.println() ou melhor ainda aprenda a usar Log4j

      Por outro lado se precisa do ultimo da fila o seu objeto de fila deve ser uma fila (!)
      Use um LinkedList para o objeto que contêm os pedidos. Depois é só fazer

      fila.getLast();

      Isso é muiiiito mais eficiente que usar o iterator.

  4. Oi Sérgio,
    Quanto a implementação do ArrayIterator fiquei com algumas dúvidas,
    Em relação ao atributo current, pq não torna-lo private?
    Poderia o current em alguma situação ter o valor menor q -1?
    t+

    1. Pode torna current private sem problema.
      Todos os iteradores são inicializados no item anterior ao primeiro ( que é um item “virtual” pois ele não existe de fato).
      Arrays em java começam no index 0. Se queremos começar no item anterior ao zero , temos que começar em -1.
      O valor -1 aqui tem um significado e não pode ser substituido por outro numero. Não, current não pode ser menor que -1.

      1. Sendo assim, poderiamos simplificar
        16 current++;
        17 if ( current > 0 || array.length- 1 ){

        para:
        16 current++;
        17 if ( current > array.length- 1 ){

        correto?
        Considerando, é claro, o current como atributo privado.

  5. Eae cara,

    to precisando de ajuda com a interface iterable.

    tenho uma classe que guarda uma lista e quero usar o for each nela, minha classe é mais ou menos assim:

    view plaincopy to clipboardprint?

    public class NewClass implements Iterable{

    private ArrayList inteiros = new ArrayList();

    public Iterator iterator() {
    return inteiros.iterator();
    }

    public static void main( String[] args ) {

    NewClass nc = new NewClass();

    for( Integer i : nc ) { // esta dando erro aqui
    System.out.println(i);
    }

    }
    }

    como o ArrayList já retorna um Iterator achei que iria funcionar, mas esta dando erro. Alguém sabe como fazer isso?

    1. Estão faltando as assinaturas genéricas para o java entenda que o for é sobre a classe integer. neste momento ele está assumindo que é o Object e por isso dá erro ( provavelmente classCastException)

      public class NewClass implements Iterable<Integer>>{

      private ArrayList<Integer> inteiros = new ArrayList<Integer>();

      public Iterator<Integer> iterator() {
      return inteiros.iterator();
      }

      public static void main( String[] args ) {

      NewClass nc = new NewClass();

      for( Integer i : nc ) {
      System.out.println(i);
      }

      }
      }

Deixe uma resposta para Rafael Lima Vieira Cancelar resposta