Por que o Iterator do Java não é um Iterable?


178

Por que a Iteratorinterface não se estende Iterable?

O iterator()método poderia simplesmente retornar this.

É de propósito ou apenas uma supervisão dos designers de Java?

Seria conveniente poder usar um loop for-each com iteradores como este:

for(Object o : someContainer.listSomeObjects()) {
    ....
}

onde listSomeObjects()retorna um iterador.


1
OK - Eu vejo o seu ponto de vista. ainda seria conveniente mesmo se quebrou um pouco semântica:] Obrigado U para todas as respostas:]
Łukasz Bownik

Sei que essa pergunta foi feita há muito tempo, mas - você quer dizer algum iterador ou apenas um iterador relacionado a alguma coleção?
einpoklum

Respostas:


67

Porque um iterador geralmente aponta para uma única instância em uma coleção. Iterable implica que se pode obter um iterador de um objeto para percorrer seus elementos - e não há necessidade de iterar em uma única instância, que é o que um iterador representa.


25
+1: uma coleção é iterável. Um iterador não é iterável porque não é uma coleção.
5609 S.Lott

50
Enquanto concordo com a resposta, não sei se concordo com a mentalidade. A interface Iterable apresenta um único método: Iterator <?> Iterator (); Em qualquer caso, eu devo poder especificar um iterador para cada um. Eu não compro.
Chris K

25
@ S.Lott bom raciocínio circular lá.
yihtserns

16
@ S.Lott Última tentativa: Coleção ∈ Iterável. Iterator ≠ Coleção ∴ Iterator ∉ Iterable
yihtserns

27
@ S.Lott: Coleção é totalmente irrelevante para esta discussão. A coleção é apenas uma das muitas implementações possíveis do Iterable. O fato de algo não ser uma coleção não tem influência sobre se é um iterável.
ColinD 28/07

218

Um iterador é stateful. A idéia é que, se você ligar Iterable.iterator()duas vezes, obterá iteradores independentes - para a maioria dos iteráveis, de qualquer maneira. Isso claramente não seria o seu caso.

Por exemplo, eu normalmente posso escrever:

public void iterateOver(Iterable<String> strings)
{
    for (String x : strings)
    {
         System.out.println(x);
    }
    for (String x : strings)
    {
         System.out.println(x);
    }
}

Isso deve imprimir a coleção duas vezes - mas com o seu esquema o segundo loop sempre termina instantaneamente.


17
@ Chris: Se uma implementação retornar o mesmo iterador duas vezes, como diabos poderia cumprir o contrato do Iterator? Se você chamar iteratore usar o resultado, ele terá que repetir a coleção - o que não acontecerá se o mesmo objeto já tiver repetido a coleção. Você pode fornecer qualquer implementação correta (exceto para uma coleção vazia) em que o mesmo iterador é retornado duas vezes?
21911 Jon Skeet

7
Esta é uma ótima resposta Jon, você realmente tem o ponto crucial do problema aqui. Pena que esta não é a resposta aceita! O contrato para o Iterable é estritamente definido, mas o acima é uma ótima explicação dos motivos pelos quais permitir que o Iterator implemente o Iterable (para foreach) quebraria o espírito da interface.
Joelittlejohn

3
@JonSkeet enquanto um Iterador <T> é estável, o contrato do Iterable <T> não diz nada sobre poder ser usado duas vezes para obter iteradores independentes, mesmo que seja o cenário em 99% dos casos. Tudo o que o Iterável <T> diz é que ele permite que um objeto seja o alvo do foreach. Para aqueles que estão descontentes com o Iterator <T> não sendo um Iterável <T>, você é livre para fazer esse Iterator <T>. Não quebraria o contrato nem um pouco. No entanto, o próprio iterador não deve ser iterável, pois isso o tornaria dependente circularmente e cria as bases para um design nojento.
Centril

2
@Centril: Certo. Editei para indicar que geralmente chamar iterableduas vezes fornecerá iteradores independentes.
Jon Skeet

2
Isso chega ao cerne disso. Seria quase possível implementar um Iterador Iterável que implementa .iterator (), redefinindo-se, mas esse design ainda seria interrompido em algumas circunstâncias, por exemplo, se fosse passado para um método que pega um Iterável e faz um loop em todos os pares possíveis de elementos aninhando para cada loop.
Theodore Murdock

60

Pelos meus US $ 0,02, concordo plenamente que o Iterator não deve implementar o Iterable, mas acho que o loop for aprimorado também deve aceitar. Eu acho que todo o argumento "torne os iteradores iteráveis" surge como uma solução para um defeito no idioma.

Todo o motivo da introdução do loop for aprimorado foi que "elimina a labuta e a propensão a erros dos iteradores e variáveis ​​de índice ao iterar sobre coleções e matrizes" [ 1 ].

Collection<Item> items...

for (Iterator<Item> iter = items.iterator(); iter.hasNext(); ) {
    Item item = iter.next();
    ...
}

for (Item item : items) {
    ...
}

Por que, então, esse mesmo argumento não se aplica aos iteradores?

Iterator<Iter> iter...
..
while (iter.hasNext()) {
    Item item = iter.next();
    ...
}

for (Item item : iter) {
    ...
}

Nos dois casos, as chamadas para hasNext () e next () foram removidas e não há referência ao iterador no loop interno. Sim, entendo que os Iterables podem ser reutilizados para criar vários iteradores, mas isso acontece fora do loop for: dentro do loop, só há uma progressão para a frente um item de cada vez nos itens retornados pelo iterador.

Além disso, permitir isso também tornaria fácil o uso do loop for para enumerações, que, como foi apontado em outro lugar, são análogas a Iteradores e não Iterables.

Portanto, não faça o Iterator implementar Iterable, mas atualize o loop for para aceitar qualquer um.

Felicidades,


6
Concordo. Teoricamente, pode haver confusão ao obter um iterador, usar parte dele e colocá-lo em um foreach (quebrando o contrato "cada" de foreach)), mas não acho que seja uma razão suficientemente boa para não ter esse recurso.
Bart van Heukelom 28/10

Já atualizamos / aprimoramos o loop para aceitar matrizes em vez de criar uma coleção iterável de matrizes. Você pode racionalizar essa decisão?
Val

Votei positivamente nesta resposta e foi um erro. O iterador é estável, se você promover a iteração sobre um iterador com a for(item : iter) {...}sintaxe, ele provocará um erro quando o mesmo iterador for iterado duas vezes. Imagine que Iteratoré passado para iterateOvermétodo em vez de Iterablenos este exemplo .
Ilya Silvestrov

2
Realmente não importa se você usa o estilo for (String x : strings) {...}ou while (strings.hasNext()) {...}: se você tentar percorrer um iterador duas vezes na segunda vez, não produzirá resultados, então não vejo isso por si só como um argumento contra permitir a sintaxe aprimorada. A resposta de Jon é diferente, porque ele está mostrando como envolver um Iteratorem um Iterablecausaria problemas, pois nesse caso você esperaria poder reutilizá-lo quantas vezes quiser.
Barney #

17

Como apontado por outros, Iteratore Iterablesão duas coisas diferentes.

Além disso, as Iteratorimplementações anteriores ao aprimoramento para loops.

Também é trivial superar essa limitação com um método adaptador simples que se parece com isso quando usado com importações de métodos estáticos:

for (String line : in(lines)) {
  System.out.println(line);
}

Exemplo de implementação:

  /**
   * Adapts an {@link Iterator} to an {@link Iterable} for use in enhanced for
   * loops. If {@link Iterable#iterator()} is invoked more than once, an
   * {@link IllegalStateException} is thrown.
   */
  public static <T> Iterable<T> in(final Iterator<T> iterator) {
    assert iterator != null;
    class SingleUseIterable implements Iterable<T> {
      private boolean used = false;

      @Override
      public Iterator<T> iterator() {
        if (used) {
          throw new IllegalStateException("SingleUseIterable already invoked");
        }
        used = true;
        return iterator;
      }
    }
    return new SingleUseIterable();
  }

No Java 8, adaptar um Iteratora um Iterablefica mais simples:

for (String s : (Iterable<String>) () -> iterator) {

você declarou uma classe em uma função; estou esquecendo de algo? Eu pensei que isso era ilegal.
Activedecay 13/03/19

Uma classe pode ser definida em um bloco em Java. É chamado classe local
Colin D Bennett

3
Obrigado pelafor (String s : (Iterable<String>) () -> iterator)
AlikElzin-Kilaka

8

Como já foi dito, um Iterable pode ser chamado várias vezes, retornando um Iterator novo a cada chamada; um iterador é usado apenas uma vez. Portanto, eles estão relacionados, mas servem a propósitos diferentes. Frustrantemente, no entanto, o método "compactar para" funciona apenas com um iterável.

O que descreverei abaixo é uma maneira de obter o melhor dos dois mundos - retornando um Iterável (para uma sintaxe mais agradável), mesmo quando a sequência de dados subjacente é única.

O truque é retornar uma implementação anônima do Iterable que realmente aciona o trabalho. Portanto, em vez de fazer o trabalho que gera uma sequência pontual e, em seguida, retornar um Iterator sobre isso, você retorna um Iterable que, cada vez que é acessado, refaz o trabalho. Isso pode parecer um desperdício, mas muitas vezes você chamará o Iterable de qualquer maneira e, mesmo que o chame várias vezes, ele ainda terá uma semântica razoável (ao contrário de um invólucro simples que faz com que um Iterador "pareça" um Iterável, isso é válido ' t falhar se usado duas vezes).

Por exemplo, digamos que eu tenho um DAO que fornece uma série de objetos de um banco de dados e desejo fornecer acesso a ele por meio de um iterador (por exemplo, para evitar a criação de todos os objetos na memória, se não forem necessários). Agora eu poderia simplesmente retornar um iterador, mas isso torna feio o uso do valor retornado. Então, em vez disso, envolvo tudo em um Iterable anon:

class MetricDao {
    ...
    /**
     * @return All known metrics.
     */
    public final Iterable<Metric> loadAll() {
        return new Iterable<Metric>() {
            @Override
            public Iterator<Metric> iterator() {
                return sessionFactory.getCurrentSession()
                        .createQuery("from Metric as metric")
                        .iterate();
            }
        };
    }
}

isso pode ser usado em código como este:

class DaoUser {
    private MetricDao dao;
    for (Metric existing : dao.loadAll()) {
        // do stuff here...
    }
}

o que me permite usar o loop for compacto, mantendo o uso incremental de memória.

Essa abordagem é "preguiçosa" - o trabalho não é feito quando o Iterable é solicitado, mas somente mais tarde quando o conteúdo é repetido - e você precisa estar ciente das consequências disso. No exemplo com um DAO, isso significa iterar sobre os resultados na transação do banco de dados.

Portanto, existem várias advertências, mas isso ainda pode ser um idioma útil em muitos casos.


agradáveis ans, mas a sua returning a fresh Iterator on each calldeve ser acompanhado com porque isto é para evitar problema de concorrência ...
Anirudha

7

Incrivelmente, ninguém mais deu essa resposta ainda. Veja como você pode iterar "facilmente" sobre um Iteratorusando o novo Iterator.forEachRemaining()método Java 8 :

Iterator<String> it = ...
it.forEachRemaining(System.out::println);

Obviamente, existe uma solução "mais simples" que trabalha diretamente com o loop foreach, envolvendo o Iteratorin a Iterablelambda:

for (String s : (Iterable<String>) () -> it)
    System.out.println(s);

5

Iteratoré uma interface que permite iterar sobre algo. É uma implementação de percorrer uma coleção de algum tipo.

Iterable é uma interface funcional que indica que algo contém um iterador acessível.

No Java8, isso facilita bastante a vida ... Se você tem um, Iteratormas precisa de um, Iterablepode simplesmente fazer:

Iterator<T> someIterator;
Iterable<T> = ()->someIterator;

Isso também funciona no loop for:

for (T item : ()->someIterator){
    //doSomething with item
}

2

Concordo com a resposta aceita, mas quero adicionar minha própria explicação.

  • O iterador representa o estado do deslocamento, por exemplo, você pode obter o elemento atual de um iterador e avançar para o próximo.

  • Iterable representa uma coleção que pode ser percorrida, pode retornar quantos iteradores você desejar, cada um representando seu próprio estado de deslocamento, um iterador pode estar apontando para o primeiro elemento, enquanto outro pode estar apontando para o terceiro elemento.

Seria bom se o Java for loop aceite o Iterator e o Iterable.


2

Para evitar dependência do java.utilpacote

De acordo com o JSR original, Um loop for aprimorado para a Java ™ Programming Language , as interfaces propostas:

  • java.lang.Iterable
  • java.lang.ReadOnlyIterator
    (proposto para ser adaptado java.util.Iterator, mas aparentemente isso nunca aconteceu)

… Foram projetados para usar o java.langnamespace do pacote em vez de java.util.

Para citar o JSR:

Essas novas interfaces servem para evitar a dependência do idioma em java.util que, de outra forma, resultaria.


A propósito, o antigo java.util.Iterableganhou um novo forEachmétodo no Java 8+, para uso com a sintaxe lambda (passando a Consumer).

Aqui está um exemplo. A Listinterface estende a Iterableinterface, como qualquer lista carrega um forEachmétodo.

List
.of ( "dog" , "cat" , "bird" )
.forEach ( ( String animal ) -> System.out.println ( "animal = " + animal ) );

2

Eu também vejo muitos fazendo isso:

public Iterator iterator() {
    return this;
}

Mas isso não faz tudo certo! Este método não seria o que você deseja!

O método iterator()deve retornar um novo iterador começando do zero. Então é preciso fazer algo assim:

public class IterableIterator implements Iterator, Iterable {

  //Constructor
  IterableIterator(SomeType initdata)
  {
    this.initdata = iter.initdata;
  }
  // methods of Iterable

  public Iterator iterator() {
    return new IterableIterator(this.intidata);
  }

  // methods of Iterator

  public boolean hasNext() {
    // ...
  }

  public Object next() {
    // ...
  }

  public void remove() {
    // ...
  }
}

A questão é: haveria alguma maneira de fazer uma classe abstrata realizando isso? Para que um IterableIterator seja necessário, basta implementar os dois métodos next () e hasNext ()


1

Se você veio aqui em busca de uma solução alternativa, pode usar IteratorIterable . (disponível para Java 1.6 e superior)

Exemplo de uso (reversão de um vetor).

import java.util.Vector;
import org.apache.commons.collections4.iterators.IteratorIterable;
import org.apache.commons.collections4.iterators.ReverseListIterator;
public class Test {
    public static void main(String ... args) {
        Vector<String> vs = new Vector<String>();
        vs.add("one");
        vs.add("two");
        for ( String s: vs ) {
            System.out.println(s);
        }
        Iterable<String> is
            = new IteratorIterable(new ReverseListIterator(vs));
        for ( String s: is ) {
            System.out.println(s);
        }
    }
}

impressões

one
two
two
one

0

Por uma questão de simplicidade, Iterator e Iterable são dois conceitos distintos, Iterable é simplesmente uma abreviação de "Posso retornar um Iterador". Eu acho que seu código deve ser:

for(Object o : someContainer) {
}

com alguma instânciaContainer SomeContainer extends Iterable<Object>




0

Os iteradores são stateful, eles têm um "próximo" elemento e ficam "exaustos" depois de iterados. Para ver onde está o problema, execute o código a seguir, quantos números são impressos?

Iterator<Integer> iterator = Arrays.asList(1,2,3).iterator();
Iterable<Integer> myIterable = ()->iterator;
for(Integer i : myIterable) System.out.print(i);
System.out.println();
for(Integer i : myIterable) System.out.print(i);

-1

Você pode tentar o seguinte exemplo:

List ispresent=new ArrayList();
Iterator iterator=ispresent.iterator();
while(iterator.hasNext())
{
    System.out.println(iterator.next());
}
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.