2010-04-08 12 views
16

Ho letto Why is Java's Iterator not an Iterable? e Why aren't Enumerations Iterable?, ma io ancora non capisco perché questo:Java: perché non è possibile eseguire iterazioni su un iteratore?

void foo(Iterator<X> it) { 
    for (X x : it) { 
    bar(x); 
    baz(x); 
    } 
} 

non è stato reso possibile. In altre parole, a meno che non mi manca qualcosa, quanto sopra avrebbe potuto essere bello e valida zucchero sintattico per:

void foo(Iterator<X> it) { 
    for (X x; it.hasNext();) { 
    x = it.next(); 
    bar(x); 
    baz(x); 
    } 
} 

risposta

7

ma non riesco ancora a capire perché questo [...] non sia stato reso possibile.

Vedo diverse ragioni:

  1. Iterator s non sono riutilizzabili, quindi una per/ogni consumerebbe l'iteratore - comportamento non corretto, forse, ma poco intuitivo a coloro che non sanno come il per/ciascuno è il desugared.
  2. Iterator s non appaiono "nudi" nel codice tutto ciò spesso, quindi complicherebbe il JLS con scarso guadagno (il costrutto/per ogni costrutto è già abbastanza cattivo, lavorando su entrambi gli Iterable e gli array).
  3. C'è un easy workaround. Può sembrare un po 'dispendioso allocare un nuovo oggetto solo per questo, ma l'allocazione è a buon mercato così com'è e l'analisi di fuga ti libererebbe anche di quel piccolo costo nella maggior parte dei casi. (Perché non hanno incluso questa soluzione in una classe di utilità Iterables, analogo a Collections e Arrays, è oltre me, però.)
  4. (probabilmente non è vero -. Vedi i commenti) Mi sembra di ricordare che la JLS può solo riferimenti in java.lang[citazione necessaria], quindi avrebbero dovuto creare un'interfaccia Iterator in java.lang che java.util.Iterator si estende senza aggiungere nulla a. Ora abbiamo due interfacce iteratore equivalenti dal punto di vista operativo. Il 50% del nuovo codice che utilizza gli iteratori nudi sceglierà la versione java.lang, il resto utilizzerà quella in java.util. Si manifesta il caos, problemi di compatibilità abbondano, ecc

Credo che i punti 1-3 sono molto in linea con il modo la filosofia progettuale linguaggio Java sembra andare: non sorprendere i nuovi arrivati, non complicare la spec se non ha un guadagno chiaro che oscura i costi, e non fare con una funzionalità linguistica cosa può essere fatto con una libreria.

Gli stessi argomenti spiegherebbero perché java.util.Enumeration non è Iterable.

+0

"Mi sembra di ricordare che JLS può solo fare riferimento a cose in java.lang" Ignorando qualsiasi contesto di 'discussione' o 'esempio' (di cui ce ne sono molti), il JLS si riferisce più volte a java.io.Serializable (§4.10.3 e altri) come caso speciale (es. Serializable è un supertipo valido di una matrice primitiva). Irrilevante per la discussione in corso, ma interessante comunque. – Cowan

+0

Non c'è 'java.util.Enumerator'. Intendevi "Enumerazione"? –

+0

@Cowan, grazie. Ho anche guardato il JLS ma sono riuscito a perdere quelle parti. @ Joachim, l'ho fatto. Aggiornato il mio post. – gustafc

17

Molto probabilmente la ragione di questo è perché iteratori non sono riutilizzabili; è necessario ottenere un nuovo Iterator dalla raccolta Iterable ogni volta che si desidera eseguire un'iterazione sugli elementi. Tuttavia, come una soluzione rapida:

private static <T> Iterable<T> iterable(final Iterator<T> it){ 
    return new Iterable<T>(){ public Iterator<T> iterator(){ return it; } }; 
} 

//.... 
{ 
    // ... 
    // Now we can use: 
    for (X x : iterable(it)){ 
     // do something with x 
    } 
    // ... 
} 
//.... 

Detto questo, la cosa migliore da fare è semplicemente passare intorno l'interfaccia Iterable<T> invece di Iterator<T>

+0

+1: con gli array e 'Iterable' è possibile scrivere lo stesso loop due volte di fila e lo hanno lavorare come previsto Questo non funziona con 'Iterator'. –

+0

Hai ragione sarebbe meglio, ma sto usando il mio iteratore su qualcosa che non è una collezione reale (è un NodeList DOM). Altrimenti, la tua risposta ha un senso, grazie. – noamtm

+5

@noamtm: se non è un 'Collection' ma può fornire un' Iterator', allora probabilmente dovrebbe implementare 'Iterable'. –

6

La sintassi for(Type t : iterable) è valido solo per le classi che implementano Iterable<Type>.

Un iteratore non implementa iterabile.

È possibile eseguire iterazioni su cose come Collection<T>, List<T> o Set<T> perché implementano Iterable.

Il seguente codice è equivalente:

for (Type t: list) { 
    // do something with t 
} 

e

Iterator<Type> iter = list.iterator(); 
while (iter.hasNext()) { 
    t = iter.next(); 
    // do something with t 
} 

La ragione per cui questo non è stato reso possibile, è perché la for-each sintassi è stato aggiunto al linguaggio di astrarre il Iterator . Fare in modo che il ciclo for-each funzioni con gli iteratori non realizzerebbe per che cosa è stato creato il ciclo for-each.

+3

Ovvio, non risponde alla mia domanda "perché non è stato reso possibile". – noamtm

+1

Una 'Mappa' (che sarebbe' Mappa ', btw) * non * implementa' Iterable'. Dovresti usare 'keySet()', 'values ​​()' o 'entrySet()' e scorrere su quelli. –

+0

@Joach Il mio male, stavo pensando a C#. – jjnguy

2

Gli iteratori non devono essere riutilizzati (vale a dire utilizzati in più di un ciclo di iterazione). In particolare, Iterator.hasNext() garantisce che è possibile chiamare in modo sicuro Iterator.next() e in effetti ottenere il valore successivo dalla raccolta sottostante.

Quando lo stesso iteratore viene utilizzato in due iterazioni in esecuzione contemporaneamente (Ipotizziamo uno scenario multi-threading), questa promessa non può più essere mantenuta:

while(iter.hasNext() { 
    // Now a context switch happens, another thread is performing 
    // iter.hasNext(); x = iter.next(); 

    String s = iter.next(); 
      // A runtime exception is thrown because the iterator was 
      // exhausted by the other thread 
} 

Tali scenari rompere completamente il protocollo offerto da Iterator. In realtà, possono verificarsi anche in un singolo programma con thread: un ciclo di iterazione chiama un altro metodo che utilizza lo stesso iteratore per eseguire la propria iterazione. Quando viene restituito questo metodo, il chiamante sta emettendo una chiamata Iterator.next() che, ancora, non riesce.

0

Poiché la for-each è progettato per leggere come qualcosa di simile:

for each element of [some collection of elements] 

Un Iterator non è [some collection of elements]. Un array e un Iterable è.

3

In realtà, è possibile.

C'è molto breve soluzione disponibile su Java 8:

for (X item : (Iterable<X>)() -> iterator) 

Vedere How to iterate with foreach loop over java 8 stream per la spiegazione dettagliata del trucco.

E alcune spiegazioni perché questo non è stato nativamente supportati si trova in domanda relativa:

Why does Stream<T> not implement Iterable<T>?

+0

Grazie, ma in realtà non risponde alla mia domanda. La domanda (dal 2010) riguardava davvero la scelta del design (da qui "perché", non "come"). +1 perché è bello sapere, però. – noamtm

Problemi correlati