Warum erlaubt Java kein Foreach für Iteratoren (nur für Iterables)? [duplicate]

Lesezeit: 9 Minuten

Benutzer-Avatar
Hat QUIT–Anony-Mousse

Mögliches Duplikat:

Warum ist der Iterator von Java kein Iterable?

Idiomatische Art, for-each-Schleife bei einem Iterator zu verwenden?

Können wir die for-each-Schleife zum Iterieren der Objekte vom Typ Iterator verwenden?

Die Foreach-Schleife sind meines Wissens Syntaxzucker, der in Java 5 hinzugefügt wurde. Also

Iterable<O> iterable;
for(O o : iterable) {
    // Do something
}

erzeugt im Wesentlichen den gleichen Bytecode wie

Iterable<O> iterable;
for(Iterator<O> iter = iterable.iterator(); iter.hasNext(); /* NOOP */) {
    O o = iter.next();
    // Do something
}

Wenn ich aber gar kein Iterable habe, sondern nur einen Iterator (z. B. weil eine Klasse zwei verschiedene Iteratoren anbietet), kann ich die Syntax sugar foreach loop nicht verwenden. Offensichtlich kann ich immer noch die einfache Iteration im alten Stil durchführen. Allerdings würde ich eigentlich gerne machen:

Iterator<O> iter;
for(O o : iter /* Iterator<O>, not Iterable<O>! */) {
     // Do something
}

Und natürlich kann ich eine Fälschung machen Iterable:

class Adapter<O> implements Iterable<O> {
    Iterator<O> iter;

    public Adapter(Iterator<O> iter) {
        this.iter = iter;
    }

    @Override
    public Iterator<O> iterator() {
        return iter;
    }
}

(Was in der Tat ein hässlicher Missbrauch der Iterable API ist, da sie nur einmal iteriert werden kann!)

Wenn es herum konstruiert wäre Iterator Anstelle von Iterable könnte man eine Reihe interessanter Dinge tun:

for(O o : iterable.iterator()) {} // Iterate over Iterable and Collections

for(O o : list.backwardsIterator()) {} // Or backwards

Iterator<O> iter;
for(O o : iter) {
    if (o.something()) { iter.remove(); }
    if (o.something()) { break; }
}
for(O : iter) { } // Do something with the remaining elements only.

Weiß jemand warum wurde die Sprache so gestaltet? Um Mehrdeutigkeiten zu vermeiden, ob eine Klasse beides implementieren würde Iterator und Iterable? Um Programmierfehler zu vermeiden, die davon ausgehen, dass “for(O o : iter)” alle Elemente zweimal verarbeitet (und vergisst, einen neuen Iterator zu erhalten)? Oder gibt es dafür einen anderen Grund?

Oder gibt es einen Sprachtrick, den ich einfach nicht kenne?

  • Oder dieses.

    – David Newton

    26. Juni 2012 um 22:53 Uhr

  • Beide schließen Duplikate, aber nicht ganz gleich. Iterator sollte nicht Iterable sein, da Sie nur einmal iterieren können (keine “Neustarts”). Und ich weiß, dass Sie foreach nicht für Iteratoren verwenden können (es sei denn, Sie verwenden den Adapter, den ich skizziert habe), aber ich versuche zu verstehen, warum Java auf diese Weise entworfen wurde.

    – Hat QUIT–Anony-Mousse

    26. Juni 2012 um 23:11 Uhr

  • Meh; Ich bin eher anderer Meinung. Die Antwort in den 1.5-Dokumenten besagt einfach, dass die Designer diesen Weg gewählt haben, um die häufigsten Anwendungsfälle zu handhaben. Manchmal die Antwort ist “nur weil”.

    – David Newton

    26. Juni 2012 um 23:13 Uhr

  • Manchmal die Designer haben ein Grund, die Sprache auf die eine oder andere Weise zu gestalten … was ist falsch daran, zu versuchen, ihre Argumentation zu verstehen (auch wenn ich nicht in der Lage sein werde, Dinge zu ändern)?

    – Hat QUIT–Anony-Mousse

    26. Juni 2012 um 23:22 Uhr

  • Wer hat gesagt, dass etwas falsch daran sei, ihre Argumentation zu verstehen? Was ich sagte war, dass sie sich entschieden haben, tief hängende Früchte zu schnappen, so haben sie es gemacht, und es gibt nicht unbedingt viel zu verstehen.

    – David Newton

    26. Juni 2012 um 23:34 Uhr

Benutzer-Avatar
Hat QUIT–Anony-Mousse

Also ich habe jetzt eine halbwegs vernünftige Erklärung:

Kurzversion: Denn die Syntax gilt auch für Arraysdie keine Iteratoren haben.

Wenn die Syntax herum entworfen wurde Iterator Wie ich vorgeschlagen habe, wäre es mit Arrays inkonsistent. Lassen Sie mich drei Varianten nennen:

EIN) wie von den Java-Entwicklern ausgewählt:

Object[] array;
for(Object o : array) { }
Iterable<Object> list;
for(Object o : list) { }
Iterator<Object> iter;
while(iter.hasNext()) { Object o = iter.next(); }

Der verhält sich genauso und ist sehr konsistent über Arrays und Sammlungen hinweg. Iteratoren müssen jedoch den klassischen Iterationsstil verwenden (der zumindest nicht wahrscheinlich Fehler verursacht).

B) Arrays zulassen und Iterators:

Object[] array;
for(Object o : array) { }
Iterable<Object> list;
for(Object o : list.iterator()) { }
Iterator<Object> iter;
for(Object o : iter) { }

Jetzt sind Arrays und Sammlungen inkonsistent; aber Arrays und ArrayList sind sehr eng verwandt und sollten sich gleich verhalten. Nun, wenn überhaupt, ist die Sprache erweitert zB Arrays implementieren zu lassen Iterablees wird inkonsequent.

C) alle drei zulassen:

Object[] array;
for(Object o : array) { }
Iterable<Object> list;
for(Object o : list) { }
Iterator<Object> iter;
for(Object o : iter) { }

Wenn wir jetzt in unklare Situationen geraten, wenn entweder jemand etwas umsetzt beide Iterable und Iterator (soll die for-Schleife einen neuen Iterator bekommen oder über den aktuellen iterieren – passiert leicht in baumartigen Strukturen!?!). Ein einfacher Tie-Braker ala “Iterable beats Iterator” reicht leider nicht aus: Er führt plötzlich zu Unterschieden zwischen Laufzeit und Kompilierzeit und zu generischen Problemen.

Jetzt müssen wir plötzlich darauf achten, ob wir Collections/Iterables oder Arrays iterieren wollen, an diesem Punkt haben wir nur sehr geringe Vorteile auf Kosten einer großen Verwirrung erzielt.

Die Art und Weise, wie “for each” in Java (A) ist, ist sehr konsistent, verursacht sehr wenige Programmierfehler und ermöglicht die mögliche zukünftige Änderung, Arrays in reguläre Objekte umzuwandeln.

Es gibt eine Variante D) das würde wahrscheinlich auch gut funktionieren: for-each nur für Iteratoren. Vorzugsweise durch Hinzufügen von a .iterator() Methode zu primitiven Arrays:

Object[] array;
for(Object o : array.iterator()) { }
Iterable<Object> list;
for(Object o : list.iterator()) { }
Iterator<Object> iter;
for(Object o : iter) { }

Dies erfordert jedoch Änderungen an der Laufzeitumgebung, nicht nur am Compiler, und bricht die Abwärtskompatibilität. Außerdem ist die erwähnte Verwirrung immer noch vorhanden

Iterator<Object> iter;
for(Object o : iter) { }
for(Object o : iter) { }

Iteriert nur einmal über die Daten.

  • “Mit der Iterator-Syntax müsste man schreiben…” Wieso den?

    – TJ Crowder

    26. Juni 2012 um 23:29 Uhr


  • Denn sonst könnte ich eine Klasse haben, die beides implementiert Iterable und Iterator und große Verwirrung stiften.

    – Hat QUIT–Anony-Mousse

    26. Juni 2012 um 23:31 Uhr

  • Und was hat das mit Arrays zu tun? Ich kann das trotzdem tun, ohne Bezug auf Arrays – und es ist exakt die Art von Komplexität, die ich in meiner Antwort anrufe (Schrägstrich “schätzen” :-)).

    – TJ Crowder

    26. Juni 2012 um 23:33 Uhr


  • Sie möchten die gleiche Syntax für Arrays haben, die Sie für Sammlungen haben, und Sammlungen sind Iterableaber nicht Iterators. Während man also auch auf Iteratoren eine For-Each-Schleife einrichten könnte, wird man sie definitiv für Arrays und Array-ähnliche Strukturen wie Sammlungen benötigen.

    – Hat QUIT–Anony-Mousse

    26. Juni 2012 um 23:38 Uhr

  • @ Anony-Mousse: Hä? Ist es jetzt Ihr Argument, dass Sie für-jeden wollen nur Arbeiten Sie an Iteratoren und nicht an Iterables? Du hast mich irgendwo verloren.

    – TJ Crowder

    26. Juni 2012 um 23:41 Uhr

Benutzer-Avatar
Assyrien

Die Iterable-Schnittstelle wurde genau für diesen Zweck erstellt (erweiterte For-Schleife), wie in beschrieben Original-JSRobwohl die Iterator-Schnittstelle bereits verwendet wurde.

In Bezug auf die neuen Schnittstellen, die in der JSR besprochen werden (beachten Sie die Paketnamen):

  • java.lang.Iterable
  • java.lang.ReadOnlyIterator (vorgeschlagen in JSR zur Nachrüstung auf java.util.Iterator obwohl nicht wirklich getan)

… sagt der JSR:

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

  • Danke für diesen Hinweis. Ich habe jetzt eine ungefähre Vorstellung davon, was der Grund sein könnte. Da muss ich noch etwas überlegen, dann aktualisiere ich.

    – Hat QUIT–Anony-Mousse

    26. Juni 2012 um 23:13 Uhr

  • Anscheinend die Begründung für die Einführung java.lang.Iterable anstatt nur zu verwenden java.util.Iterator ist, eine Kernsprachenabhängigkeit zu vermeiden java.util. 😉

    – 200_Erfolg

    30. April 2014 um 19:04 Uhr

Benutzer-Avatar
Matt

Weil die “for”-Schleife für den Iterator destruktiv wäre. Der Iterator kann nicht zurückgesetzt (dh an den Anfang zurückbewegt) werden, es sei denn, er implementiert die Subschnittstelle ListIterator.

Sobald Sie einen Iterator durch eine „for“-Schleife führen, ist er nicht mehr verwendbar. Ich vermute, dass die Sprachdesigner entschieden haben, dass dies in Kombination mit den zusätzlichen Sonderfällen (von denen es bereits zwei für Iterable und Arrays gibt) im Compiler, um dies in Bytecode zu übersetzen (Sie konnten nicht dieselbe Transformation wie Iterable wiederverwenden), ausreichte ein Kritiker, es nicht umzusetzen.

Wenn Sie dies selbst im Code über die Iterator-Schnittstelle tun, wäre es zumindest scheinbar offensichtlich, was los ist.

Mit kommenden Lambdas könnten sie das schön und einfach machen:

Iterator<String> iterator = ...;
Collections.each ( iterator, (String s) => { System.out.println(s); } );

List<String> list = ...;
Collections.each ( list, (String s) => { System.out.println(s); } );

ohne Abwärtskompatibilität zu brechen und trotzdem eine relativ einfache Syntax zu haben. Ich bezweifle, dass sie Methoden wie “each”, “collect” und “map” in die verschiedenen Schnittstellen einbauen würden, da dies die Abwärtskompatibilität brechen würde und Sie noch Arrays behandeln müssten.

  • Naja, wenn der “Reset” explizit per Anruf erfolgt .iterator() an Iterables, das Zurücksetzen sollte auch offensichtlich sein. Naja, man kann wohl noch über das Nicht-Reseten hinwegsehen…

    – Hat QUIT–Anony-Mousse

    26. Juni 2012 um 23:46 Uhr

  • Iterable bietet Ihnen einen vorhersagbaren Schleifenmechanismus, da das mehrmalige Durchlaufen einer Iterable jedes Mal das gleiche Ergebnis liefert (vorausgesetzt, Sie haben nicht mit den Daten selbst herumgespielt). Dasselbe kann nicht gesagt werden, wenn Sie einen Iterator durchlaufen könnten (da er, wie gesagt, nicht zurückgesetzt werden kann).

    – Matt

    27. Juni 2012 um 0:01 Uhr

  • Für einige Datentypen wie Warteschlangen ist der Begriff eines verbrauchenden „für jeden“ sinnvoll. Zulassen, dass Typen “für-jeden-fähig” sind, ohne ein Versprechen zu geben, dass sie es sind wiederholt aufzählbar, scheint eine hilfreiche Fähigkeit zu sein, und Iteratoren sind von Natur aus in der Lage, genau einmal gelesen zu werden.

    – Superkatze

    29. April 2014 um 20:25 Uhr

  • Diese Meinung steht im Widerspruch zu Try-with-Ressourcen und Stream.forEach()

    – Lukas Eder

    20. Dezember 2016 um 19:30 Uhr

Benutzer-Avatar
Jochen

Ich denke, ein Teil der Antwort könnte in der Tatsache verborgen sein, dass die for-each-Schleife syntaktischer Zucker ist. Der Punkt ist, dass Sie etwas, das die Leute oft tun, viel einfacher machen wollen. Und (zumindest meiner Erfahrung nach) die Redewendung

Iterator iterator = iterable.iterator();
while( iterator.hasNext() ) {
  Element e = (Element)iterator.next() ;
}

trat die ganze Zeit in Code im alten Stil auf. Und ausgefallene Dinge mit mehreren Iteratoren zu tun, tat dies nicht.

1227240cookie-checkWarum erlaubt Java kein Foreach für Iteratoren (nur für Iterables)? [duplicate]

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy