Perché Iterator di Java non è Iterable?


178

Perché l' Iteratorinterfaccia non si estende Iterable?

Il iterator()metodo potrebbe semplicemente tornare this.

È apposta o solo una svista dei progettisti di Java?

Sarebbe conveniente poter usare un ciclo for-each con iteratori come questo:

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

dove listSomeObjects()restituisce un iteratore.


1
OK - Vedo il tuo punto. sarebbe comunque conveniente anche se rompesse un po 'la semantica:] Grazie a U per tutte le risposte:]
Łukasz Bownik

Mi rendo conto che questa domanda è stata posta molto tempo fa, ma ... intendi un Iteratore o solo un Iteratore relativo a qualche Collezione?
einpoklum,

Risposte:


67

Perché un iteratore generalmente indica una singola istanza in una raccolta. Iterable implica che uno può ottenere un iteratore da un oggetto per attraversare i suoi elementi - e non è necessario iterare su una singola istanza, che è ciò che rappresenta un iteratore.


25
+1: una raccolta è iterabile. Un iteratore non è iterabile perché non è una raccolta.
S.Lott

50
Mentre sono d'accordo con la risposta, non so se sono d'accordo con la mentalità. L'interfaccia Iterable presenta un singolo metodo: Iterator <?> Iteratore (); In ogni caso, dovrei essere in grado di specificare un iteratore per for-each. Non lo compro.
Chris K,

25
@ S. Lott bel ragionamento circolare lì.
yihtserns,

16
@ S.Lott Ultimo tentativo: raccolta ∈ Iterable. Iterator ≠ Collection ∴ Iterator ∉ Iterable
yihtserns

27
@ S.Lott: la raccolta è totalmente irrilevante per questa discussione. Collection è solo una delle molte possibili implementazioni di Iterable. Il fatto che qualcosa non sia una Collezione non ha alcuna influenza sul fatto che sia Iterabile.
ColinD,

218

Un iteratore è con stato. L'idea è che se chiami Iterable.iterator()due volte otterrai iteratori indipendenti , comunque per la maggior parte degli iterabili. Questo chiaramente non sarebbe il caso nel tuo scenario.

Ad esempio, di solito posso scrivere:

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

Ciò dovrebbe stampare la raccolta due volte, ma con il tuo schema il secondo ciclo terminerebbe sempre all'istante.


17
@Chris: se un'implementazione restituisce lo stesso iteratore due volte, come può adempiere al contratto di Iterator? Se chiami iteratore usi il risultato, deve scorrere l'iter della raccolta, cosa che non farà se lo stesso oggetto ha già ripetuto l'insieme. Puoi dare qualsiasi corretta applicazione (se non per un insieme vuoto) in cui lo stesso iteratore viene restituita due volte?
Jon Skeet,

7
Questa è una grande risposta, Jon, hai davvero il nocciolo del problema qui. Peccato che questa non sia la risposta accettata! Il contratto per Iterable è rigorosamente definito, ma quanto sopra è una grande spiegazione dei motivi per cui consentire a Iterator di implementare Iterable (per foreach) avrebbe spezzato lo spirito dell'interfaccia.
joelittlejohn,

3
@JonSkeet mentre un Iteratore <T> è con stato, il contratto di Iterable <T> non dice nulla sulla possibilità di essere usato due volte per ottenere iteratori indipendenti, anche se è lo scenario nel 99% dei casi. Tutto ciò che Iterable <T> dice è che consente a un oggetto di essere il bersaglio di foreach. Per quelli di voi che non sono soddisfatti del fatto che Iterator <T> non sia Iterable <T>, siete liberi di creare un tale Iterator <T>. Non romperebbe un po 'il contratto. Tuttavia - Iterator stesso non dovrebbe essere Iterable poiché ciò lo renderebbe circolarmente dipendente e che getta le basi per un design icky.
Centril,

2
@Centril: giusto. Ho modificato per indicare che di solito chiamare iterabledue volte ti darà iteratori indipendenti.
Jon Skeet,

2
Questo ci arriva al cuore. Sarebbe quasi possibile implementare un Iterable Iterator che implementa .iterator () ripristinando se stesso, ma questo progetto si spezzerebbe ancora in alcune circostanze, ad esempio, se fosse passato a un metodo che prende un Iterable e passa su tutte le coppie possibili di elementi annidando per ogni loop.
Theodore Murdock,

60

Per i miei $ 0,02, sono completamente d'accordo sul fatto che Iterator non dovrebbe implementare Iterable, ma penso che anche il ciclo avanzato migliorato dovrebbe accettare. Penso che l'intera argomentazione "rendere iteratori iterabili" emerge come una soluzione a un difetto del linguaggio.

L'intero motivo dell'introduzione del ciclo avanzato for era che "elimina la fatica e la propensione all'errore degli iteratori e delle variabili dell'indice durante l'iterazione di raccolte e matrici" [ 1 ].

Collection<Item> items...

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

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

Perché allora questo stesso argomento non vale per gli iteratori?

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

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

In entrambi i casi, le chiamate a hasNext () e next () sono state rimosse e non vi è alcun riferimento all'iteratore nel ciclo interno. Sì, capisco che gli Iterabili possono essere riutilizzati per creare più iteratori, ma tutto ciò avviene al di fuori del ciclo for: all'interno del ciclo c'è sempre e solo una progressione in avanti di un elemento alla volta sugli elementi restituiti dall'iteratore.

Inoltre, consentire ciò semplificherebbe anche l'uso del ciclo for per le enumerazioni, che, come è stato sottolineato altrove, sono analoghi agli Iteratori e non agli Iterabili.

Quindi non rendere Iterator implementare Iterable, ma aggiorna il ciclo for per accettarlo.

Saluti,


6
Sono d'accordo. Teoricamente potrebbe esserci confusione quando si ottiene un iteratore, si usa una parte di esso e poi si inserisce in una foreach (rompendo il "ogni" contratto di foreach), ma non penso che sia una ragione sufficiente per non avere questa funzionalità.
Bart van Heukelom,

Abbiamo già aggiornato / migliorato il ciclo per accettare le matrici invece di rendere le matrici collezione iterabile. Puoi razionalizzare questa decisione?
Val

Ho votato a favore di questa risposta ed è stato un errore. L'iteratore è stateful, se promuovi l'iterazione su un iteratore con la for(item : iter) {...}sintassi, provocherà un errore quando lo stesso iteratore viene ripetuto due volte. Immagina che Iteratorsia passato al iterateOvermetodo invece che Iterablein questo esempio .
Ilya Silvestrov,

2
Non importa se si utilizza lo stile for (String x : strings) {...}o while (strings.hasNext()) {...}: se si tenta di eseguire il ciclo attraverso un iteratore due volte la seconda volta non si otterranno risultati, quindi non lo vedo di per sé come argomento contro il consentire la sintassi avanzata. La risposta di Jon è diversa, perché lì sta mostrando come il confezionamento di un Iteratorin Iterablecauserebbe problemi, poiché in quel caso ti aspetteresti di essere in grado di riutilizzarlo tutte le volte che vuoi.
Barney,

17

Come sottolineato da altri, Iteratore Iterablesono due cose diverse.

Inoltre, le Iteratorimplementazioni sono migliorate per i loop.

È anche banale superare questa limitazione con un semplice metodo adattatore che assomiglia a questo se usato con importazioni di metodi statici:

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

Esempio di implementazione:

  /**
   * 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();
  }

In Java 8 l'adattamento di an Iteratora Iterablediventa più semplice:

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

hai dichiarato una classe in una funzione; mi sto perdendo qualcosa? pensavo fosse illegale.
activedecay

Una classe può essere definita in un blocco in Java. Si chiama classe locale
Colin D Bennett

3
Grazie per ilfor (String s : (Iterable<String>) () -> iterator)
AlikElzin-Kilaka

8

Come altri hanno già detto, un Iterable può essere chiamato più volte, restituendo un nuovo Iterator ad ogni chiamata; un Iteratore viene usato solo una volta. Quindi sono correlati, ma servono a scopi diversi. Frustrante, tuttavia, il metodo "compatto per" funziona solo con un iterabile.

Quello che descriverò di seguito è un modo per avere il meglio di entrambi i mondi: restituire un Iterable (per una sintassi migliore) anche quando la sequenza di dati sottostante è una tantum.

Il trucco è restituire un'implementazione anonima di Iterable che in realtà avvia il lavoro. Quindi, invece di eseguire il lavoro che genera una sequenza una tantum e quindi restituire un Iteratore su quello, si restituisce un Iterable che, ogni volta che si accede, ripete il lavoro. Ciò potrebbe sembrare dispendioso, ma spesso chiamerai l'Iterable solo una volta comunque, e anche se lo chiami più volte, ha ancora una semantica ragionevole (a differenza di un semplice wrapper che rende un Iterator "simile" a Iterable, questo ha vinto ' non fallire se usato due volte).

Ad esempio, supponiamo di avere un DAO che fornisce una serie di oggetti da un database e che voglio fornire accesso a tale tramite un iteratore (ad es. Per evitare di creare tutti gli oggetti in memoria se non sono necessari). Ora potrei semplicemente restituire un iteratore, ma ciò rende brutto l'utilizzo del valore restituito in un ciclo. Quindi invece avvolgo tutto in un altro Iterable:

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();
            }
        };
    }
}

questo può quindi essere utilizzato in codice come questo:

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

che mi permette di usare il compatto per loop pur mantenendo un uso incrementale della memoria.

Questo approccio è "pigro" - il lavoro non viene svolto quando viene richiesto l'Iterable, ma solo successivamente quando i contenuti vengono ripetuti - e devi essere consapevole delle conseguenze di ciò. Nell'esempio con un DAO che significa scorrere i risultati all'interno della transazione del database.

Quindi ci sono vari avvertimenti, ma questo può essere ancora un utile linguaggio in molti casi.


bello, ma returning a fresh Iterator on each calldovresti essere accompagnato dal perché, ad esempio, per prevenire problemi di concorrenza ...
Anirudha,

7

Incredibilmente, nessun altro ha ancora dato questa risposta. Ecco come è possibile "facilmente" scorrere su un Iteratorutilizzando il nuovo Iterator.forEachRemaining()metodo Java 8 :

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

Naturalmente, c'è una soluzione "più semplice" che funziona direttamente con il ciclo foreach, avvolgendo il Iteratorin un Iterablelambda:

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

5

Iteratorè un'interfaccia che ti consente di scorrere su qualcosa. È un'implementazione di muoversi attraverso una raccolta di qualche tipo.

Iterable è un'interfaccia funzionale che indica che qualcosa contiene un iteratore accessibile.

In Java8, questo rende la vita abbastanza semplice ... Se ne hai una Iteratorma ne Iterablehai bisogno puoi semplicemente fare:

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

Questo funziona anche nel for-loop:

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

2

Sono d'accordo con la risposta accettata, ma voglio aggiungere la mia spiegazione.

  • Iteratore rappresenta lo stato di attraversamento, ad esempio, è possibile ottenere l'elemento corrente da un iteratore e passare al successivo.

  • Iterable rappresenta una raccolta che potrebbe essere attraversata, può restituire tutti gli iteratori che desideri, ognuno dei quali rappresenta il proprio stato di attraversamento, un iteratore può indicare il primo elemento, mentre un altro può indicare il terzo elemento.

Sarebbe bello se Java per loop accetta sia Iterator che Iterable.


2

Per evitare la dipendenza dal java.utilpacchetto

Secondo l'originale JSR, Un ciclo ottimizzato per Java ™ Programming Language , le interfacce proposte:

  • java.lang.Iterable
  • java.lang.ReadOnlyIterator
    (proposto di essere adattato java.util.Iterator, ma a quanto pare non è mai successo)

... sono stati progettati per utilizzare lo java.langspazio dei nomi del pacchetto anziché java.util.

Per citare il JSR:

Queste nuove interfacce servono a prevenire la dipendenza della lingua da java.util che altrimenti risulterebbe.


A proposito, il vecchio ha java.util.Iterableottenuto un nuovo forEachmetodo in Java 8+, per l'uso con la sintassi lambda (passando a Consumer).

Ecco un esempio L' Listinterfaccia estende l' Iterableinterfaccia, poiché qualsiasi elenco contiene un forEachmetodo.

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

2

Vedo anche molti fare questo:

public Iterator iterator() {
    return this;
}

Ma questo non lo rende giusto! Questo metodo non sarebbe quello che vuoi!

Il metodo iterator()dovrebbe restituire un nuovo iteratore a partire da zero. Quindi è necessario fare qualcosa del genere:

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() {
    // ...
  }
}

La domanda è: ci sarebbe un modo per fare una lezione astratta in questo modo? In modo che per ottenere un IterableIterator basta implementare i due metodi next () e hasNext ()


1

Se sei venuto qui in cerca di una soluzione alternativa, puoi usare IteratorIterable . (disponibile per Java 1.6 e versioni successive)

Esempio di utilizzo (invertire un vettore).

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);
        }
    }
}

stampe

one
two
two
one

0

Per semplicità, Iteratore e Iterable sono due concetti distinti, Iterable è semplicemente una scorciatoia per "Posso restituire un Iteratore". Penso che il tuo codice dovrebbe essere:

for(Object o : someContainer) {
}

con qualche esempio di ContentContainer SomeContainer extends Iterable<Object>




0

Gli iteratori sono dichiarati, hanno un elemento "successivo" e diventano "sfiniti" una volta ripetuti. Per vedere dove si trova il problema, eseguire il codice seguente, quanti numeri vengono stampati?

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

Puoi provare il seguente esempio:

List ispresent=new ArrayList();
Iterator iterator=ispresent.iterator();
while(iterator.hasNext())
{
    System.out.println(iterator.next());
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.