Warum ist Javas Iterator kein Iterable?

178

Warum wird die IteratorSchnittstelle nicht erweitert Iterable?

Die iterator()Methode könnte einfach zurückkehren this.

Ist es absichtlich oder nur ein Versehen der Java-Designer?

Es wäre praktisch, eine for-each-Schleife mit Iteratoren wie diesen verwenden zu können:

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

Dabei wird listSomeObjects()ein Iterator zurückgegeben.

Łukasz Bownik
quelle
1
OK - ich sehe deinen Punkt aber. es wäre immer noch praktisch, selbst wenn es eine Semantik ein wenig
brechen würde
Mir ist klar, dass diese Frage vor langer Zeit gestellt wurde, aber - meinen Sie einen Iterator oder nur einen Iterator, der sich auf eine Sammlung bezieht?
Einpoklum

Antworten:

67

Weil ein Iterator im Allgemeinen auf eine einzelne Instanz in einer Sammlung verweist. Iterable impliziert, dass man einen Iterator von einem Objekt erhalten kann, um seine Elemente zu durchlaufen - und es besteht keine Notwendigkeit, über eine einzelne Instanz zu iterieren, was ein Iterator darstellt.

PaulJWilliams
quelle
25
+1: Eine Sammlung ist iterierbar. Ein Iterator ist nicht iterierbar, da es sich nicht um eine Sammlung handelt.
S.Lott
50
Obwohl ich der Antwort zustimme, weiß ich nicht, ob ich der Mentalität zustimme. Die Iterable-Schnittstelle bietet eine einzige Methode: Iterator <?> Iterator (); In jedem Fall sollte ich in der Lage sein, für jeden einen Iterator anzugeben. Ich kaufe es nicht.
Chris K
25
@ S.Lott nette Zirkelschlussfolgerung da.
Yihtserns
16
@ S.Lott Letzter Versuch: Sammlung ∈ Iterable. Iterator ≠ Sammlung ∴ Iterator ∉ Iterable
yihtserns
27
@ S.Lott: Sammlung ist für diese Diskussion völlig irrelevant. Die Sammlung ist nur eine von vielen möglichen Implementierungen von Iterable. Die Tatsache, dass etwas keine Sammlung ist, hat keinen Einfluss darauf, ob es ein Iterable ist.
ColinD
218

Ein Iterator ist zustandsbehaftet. Die Idee ist, dass Sie bei Iterable.iterator()zweimaligem Aufruf unabhängige Iteratoren erhalten - für die meisten Iterables jedenfalls. Dies wäre in Ihrem Szenario eindeutig nicht der Fall.

Zum Beispiel kann ich normalerweise schreiben:

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

Das sollte die Sammlung zweimal drucken - aber mit Ihrem Schema würde die zweite Schleife immer sofort beendet.

Jon Skeet
quelle
17
@Chris: Wenn eine Implementierung denselben Iterator zweimal zurückgibt, wie um alles in der Welt könnte sie den Vertrag von Iterator erfüllen? Wenn Sie iteratordas Ergebnis aufrufen und verwenden, muss es die Sammlung durchlaufen - was nicht der Fall ist, wenn dasselbe Objekt bereits die Sammlung durchlaufen hat. Können Sie eine korrekte Implementierung angeben (außer für eine leere Sammlung), bei der derselbe Iterator zweimal zurückgegeben wird?
Jon Skeet
7
Dies ist eine großartige Antwort, Jon. Hier haben Sie wirklich den Kern des Problems. Schade, dass dies nicht die akzeptierte Antwort ist! Der Vertrag für Iterable ist streng definiert, aber das Obige ist eine gute Erklärung für die Gründe, warum die Erlaubnis von Iterator zur Implementierung von Iterable (für foreach) den Geist der Schnittstelle brechen würde.
Joelittlejohn
3
@JonSkeet Während ein Iterator <T> zustandsbehaftet ist, sagt der Vertrag von Iterable <T> nichts darüber aus, dass er zweimal verwendet werden kann, um unabhängige Iteratoren zu erhalten, obwohl dies in 99% der Fälle der Fall ist. Alles, was Iterable <T> sagt, ist, dass ein Objekt das Ziel von foreach sein kann. Für diejenigen unter Ihnen, die mit Iterator <T>, der kein Iterable <T> ist, unzufrieden sind, steht es Ihnen frei, einen solchen Iterator <T> zu erstellen. Es würde den Vertrag kein bisschen brechen. Der Iterator selbst sollte jedoch nicht iterierbar sein, da dies ihn kreisförmig abhängig machen würde und den Grundstein für ein ekliges Design legt.
Centril
2
@Centril: Richtig. Haben bearbeitet, um anzuzeigen, dass normalerweiseiterable zweimaliges Aufrufen Ihnen unabhängige Iteratoren gibt.
Jon Skeet
2
Das bringt es auf den Punkt. Es wäre fast möglich, einen Iterable Iterator zu implementieren, der .iterator () implementiert, indem er sich selbst zurücksetzt. Dieses Design würde jedoch unter bestimmten Umständen immer noch nicht funktionieren, wenn es beispielsweise an eine Methode übergeben würde, die ein Iterable verwendet und alle möglichen Paare durchläuft von Elementen durch Verschachteln für jede Schleife.
Theodore Murdock
59

Für meine $ 0.02 stimme ich voll und ganz zu, dass Iterator Iterable nicht implementieren sollte, aber ich denke, dass die erweiterte for-Schleife dies auch akzeptieren sollte. Ich denke, das ganze Argument "Iteratoren iterierbar machen" kommt als Umgehung eines Sprachfehlers auf.

Der ganze Grund für die Einführung der erweiterten for-Schleife war, dass sie "die Plackerei und Fehleranfälligkeit von Iteratoren und Indexvariablen beim Iterieren über Sammlungen und Arrays beseitigt" [ 1 ].

Collection<Item> items...

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

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

Warum gilt dasselbe Argument dann nicht für Iteratoren?

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

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

In beiden Fällen wurden die Aufrufe von hasNext () und next () entfernt, und es gibt keinen Verweis auf den Iterator in der inneren Schleife. Ja, ich verstehe, dass Iterables wiederverwendet werden können, um mehrere Iteratoren zu erstellen, aber dass alles außerhalb der for-Schleife geschieht: Innerhalb der Schleife gibt es immer nur einen Vorwärtsfortschritt um jeweils ein Element über die vom Iterator zurückgegebenen Elemente.

Wenn Sie dies zulassen, wird es auch einfacher, die for-Schleife für Aufzählungen zu verwenden, die, wie an anderer Stelle erwähnt, analog zu Iteratoren und nicht zu Iterablen ist.

Lassen Sie Iterator also nicht Iterable implementieren, sondern aktualisieren Sie die for-Schleife, um sie zu akzeptieren.

Prost,

Barney
quelle
6
Genau. Theoretisch könnte es Verwirrung geben, wenn ein Iterator abgerufen, ein Teil davon verwendet und dann in einen foreach eingefügt wird (wodurch der "jeder" Vertrag von foreach gebrochen wird), aber ich denke nicht, dass dies ein guter Grund ist, diese Funktion nicht zu haben.
Bart van Heukelom
Wir haben die Schleife bereits aktualisiert / erweitert, um Arrays zu akzeptieren, anstatt Arrays iterierbar zu machen. Können Sie diese Entscheidung rationalisieren?
Val
Ich habe diese Antwort positiv bewertet, und es war ein Fehler. Iterator ist statusbehaftet. Wenn Sie die Iteration über einen Iterator mit der for(item : iter) {...}Syntax heraufstufen, wird ein Fehler ausgelöst, wenn derselbe Iterator zweimal iteriert wird. Stellen Sie sich vor , die Iteratorin übergeben wird iterateOverVerfahren statt Iterablein diesem Beispiel .
Ilya Silvestrov
2
Es spielt keine Rolle, ob Sie den Stil for (String x : strings) {...}oder verwenden while (strings.hasNext()) {...}: Wenn Sie versuchen, einen Iterator zweimal beim zweiten Mal zu durchlaufen, werden keine Ergebnisse erzielt. Ich sehe dies an sich nicht als Argument gegen das Zulassen der erweiterten Syntax. Jons Antwort ist anders, weil er dort zeigt, wie das Einwickeln eines Iteratorin ein IterableProblem verursachen würde, da Sie in diesem Fall erwarten würden, dass Sie es so oft wiederverwenden können, wie Sie möchten.
Barney
17

Wie von anderen betont, Iteratorund Iterablesind zwei verschiedene Dinge.

Außerdem sind IteratorImplementierungen älter als Schleifen.

Es ist auch trivial, diese Einschränkung mit einer einfachen Adaptermethode zu überwinden, die bei Importen statischer Methoden folgendermaßen aussieht:

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

Beispielimplementierung:

  /**
   * 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 wird das Anpassen von an Iteratoran Iterableeinfacher:

for (String s : (Iterable<String>) () -> iterator) {
McDowell
quelle
Sie haben eine Klasse in einer Funktion deklariert. vermisse ich etwas Ich dachte, das wäre illegal.
Activedecay
Eine Klasse kann in einem Block in Java definiert werden. Es heißt eine lokale Klasse
Colin D Bennett
3
Vielen Dank für diefor (String s : (Iterable<String>) () -> iterator)
AlikElzin-Kilaka
8

Wie andere gesagt haben, kann ein Iterable mehrmals aufgerufen werden, wobei bei jedem Aufruf ein neuer Iterator zurückgegeben wird. Ein Iterator wird nur einmal verwendet. Sie sind also verwandt, dienen aber unterschiedlichen Zwecken. Frustrierenderweise funktioniert die "compact for" -Methode jedoch nur mit einer iterierbaren Methode.

Was ich im Folgenden beschreiben werde, ist eine Möglichkeit, das Beste aus beiden Welten zu haben - die Rückgabe einer Iterable (für eine bessere Syntax), selbst wenn die zugrunde liegende Datenfolge einmalig ist.

Der Trick besteht darin, eine anonyme Implementierung des Iterable zurückzugeben, die die Arbeit tatsächlich auslöst. Anstatt also die Arbeit zu erledigen, die eine einmalige Sequenz generiert, und dann einen Iterator darüber zurückzugeben, geben Sie eine Iterable zurück, die bei jedem Zugriff die Arbeit wiederholt. Das mag verschwenderisch erscheinen, aber oft wird das Iterable ohnehin nur einmal aufgerufen, und selbst wenn Sie es mehrmals aufrufen, hat es dennoch eine vernünftige Semantik (im Gegensatz zu einem einfachen Wrapper, der einen Iterator wie ein Iterable "aussehen" lässt, hat dies gewonnen). t bei zweimaliger Verwendung fehlschlagen).

Angenommen, ich habe ein DAO, das eine Reihe von Objekten aus einer Datenbank bereitstellt, und ich möchte über einen Iterator Zugriff darauf gewähren (z. B. um zu vermeiden, dass alle Objekte im Speicher erstellt werden, wenn sie nicht benötigt werden). Jetzt könnte ich einfach einen Iterator zurückgeben, aber das macht die Verwendung des zurückgegebenen Werts in einer Schleife hässlich. Also wickle ich stattdessen alles in eine anon 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();
            }
        };
    }
}

Dies kann dann in Code wie folgt verwendet werden:

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

Dadurch kann ich die kompakte for-Schleife verwenden und gleichzeitig die inkrementelle Speichernutzung beibehalten.

Dieser Ansatz ist "faul" - die Arbeit wird nicht erledigt, wenn die Iterable angefordert wird, sondern erst später, wenn der Inhalt wiederholt wird - und Sie müssen sich der Konsequenzen bewusst sein. Im Beispiel mit einem DAO bedeutet dies, dass die Ergebnisse innerhalb der Datenbanktransaktion durchlaufen werden.

Es gibt also verschiedene Vorbehalte, aber dies kann in vielen Fällen immer noch eine nützliche Redewendung sein.

Andrew Cooke
quelle
nett ans, aber Sie returning a fresh Iterator on each callsollten mit warum begleitet werden, dh um Parallelitätsprobleme zu verhindern ...
Anirudha
7

Unglaublicherweise hat noch niemand diese Antwort gegeben. So können Sie Iteratormit der neuen Java 8- Iterator.forEachRemaining()Methode "einfach" über eine iterieren :

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

Natürlich gibt es eine "einfachere" Lösung, die direkt mit der foreach-Schleife zusammenarbeitet und diese Iteratorin ein IterableLambda einwickelt :

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

Iteratorist eine Schnittstelle, mit der Sie über etwas iterieren können. Es ist eine Implementierung des Durchlaufens einer Sammlung.

Iterable ist eine funktionale Schnittstelle, die angibt, dass etwas einen zugänglichen Iterator enthält.

In Java8 macht dies das Leben ziemlich einfach ... Wenn Sie eine haben, Iteratoraber eine brauchen Iterable, können Sie einfach Folgendes tun:

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

Dies funktioniert auch in der for-Schleife:

for (T item : ()->someIterator){
    //doSomething with item
}
Steve K.
quelle
2

Ich stimme der akzeptierten Antwort zu, möchte aber meine eigene Erklärung hinzufügen.

  • Iterator stellt den Zustand des Durchlaufens dar. Sie können beispielsweise das aktuelle Element von einem Iterator abrufen und zum nächsten übergehen.

  • Iterable stellt eine Sammlung dar, die durchlaufen werden kann. Es kann so viele Iteratoren zurückgeben, wie Sie möchten. Jeder repräsentiert seinen eigenen Durchlaufzustand. Ein Iterator zeigt möglicherweise auf das erste Element, während ein anderer auf das dritte Element zeigt.

Es wäre schön, wenn Java for loop sowohl Iterator als auch Iterable akzeptiert.

Dagang
quelle
2

Um die Abhängigkeit vom java.utilPaket zu vermeiden

Laut dem ursprünglichen JSR, einer erweiterten for-Schleife für die Java ™ -Programmiersprache , sind die vorgeschlagenen Schnittstellen:

  • java.lang.Iterable
  • java.lang.ReadOnlyIterator
    (vorgeschlagen, nachgerüstet zu werden java.util.Iterator, aber anscheinend ist dies nie passiert)

… Wurden entwickelt, um den java.langPaket-Namespace anstelle von zu verwendenjava.util .

So zitieren Sie den JSR:

Diese neuen Schnittstellen dienen dazu, die Abhängigkeit der Sprache von java.util zu verhindern, die sich sonst ergeben würde.


Übrigens hat das Alte in Java 8+ java.util.Iterableeine neue forEachMethode zur Verwendung mit Lambda-Syntax (Übergabe von a Consumer) erhalten.

Hier ist ein Beispiel. Die ListSchnittstelle erweitert die IterableSchnittstelle, da jede Liste eine forEachMethode enthält.

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

Ich sehe auch viele, die dies tun:

public Iterator iterator() {
    return this;
}

Aber das macht es nicht richtig! Diese Methode wäre nicht das, was Sie wollen!

Die Methode iterator()soll einen neuen Iterator zurückgeben, der von vorne beginnt. Man muss also so etwas tun:

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

Die Frage ist: Gibt es eine Möglichkeit, eine abstrakte Klasse dazu zu bringen, dies auszuführen? Um einen IterableIterator zu erhalten, müssen nur die beiden Methoden next () und hasNext () implementiert werden.

Martin Vatshelle
quelle
1

Wenn Sie auf der Suche nach einer Problemumgehung hierher gekommen sind, können Sie IteratorIterable verwenden . (verfügbar für Java 1.6 und höher)

Beispiel für die Verwendung (Umkehren eines Vektors).

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

druckt

one
two
two
one
serv-inc
quelle
0

Der Einfachheit halber sind Iterator und Iterable zwei unterschiedliche Konzepte. Iterable ist einfach eine Abkürzung für "Ich kann einen Iterator zurückgeben". Ich denke, dass Ihr Code sein sollte:

for(Object o : someContainer) {
}

mit someContainer Instanz von SomeContainer extends Iterable<Object>

dfa
quelle
0

Iteratoren sind zustandsbehaftet, sie haben ein "nächstes" Element und werden "erschöpft", sobald sie wiederholt werden. Führen Sie den folgenden Code aus, um festzustellen, wo das Problem liegt. Wie viele Zahlen werden gedruckt?

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);
Roland
quelle
-1

Sie können das folgende Beispiel ausprobieren:

List ispresent=new ArrayList();
Iterator iterator=ispresent.iterator();
while(iterator.hasNext())
{
    System.out.println(iterator.next());
}
Sakthi König
quelle