Ich arbeite nicht zu oft direkt mit Java / C # -Iteratoren, aber wenn ich das tue, frage ich mich immer, was der Grund war, Iteratoren auf "nächste" Weise zu entwerfen.
Um zu starten, müssen Sie den Iterator verschieben. Um zu überprüfen, ob Daten vorhanden sind, müssen Sie überprüfen, ob das nächste Element vorhanden ist.
Ein ansprechenderes Konzept für mich ist dieser Iterator - er "beginnt" von vorne, und Sie können diesen Zustand überprüfen, sagen wir isValid
. Die Schleife über die gesamte Sammlung würde also folgendermaßen aussehen:
while (iter.isValid())
{
println(iter.current());
iter.next();
}
Wenn Sie hier sind, um zu überprüfen, ob es das nächste Element gibt, gehen Sie dorthin und prüfen dann, ob der Status gültig ist.
YMMV aber trotzdem - gibt es einen Vorteil des nächsten Iterators (wie in Java / C #) gegenüber diesem Iterator?
Hinweis: Dies ist eine konzeptionelle Frage zum Design der Sprache.
Antworten:
Ich denke, ein Vorteil des C # /. NET-
MoveNext()
Modells gegenüber dem Java-hasNext()
Modell besteht darin, dass das erstere impliziert, dass einige Arbeiten durchgeführt werden können. DiehasNext()
Methode impliziert eine einfache Zustandsprüfung. Aber was ist, wenn Sie einen faulen Stream iterieren und in eine Datenbank gehen müssen, um festzustellen, ob Ergebnisse vorliegen? In diesem Fall kann der erste AufrufhasNext()
eine lange Blockierungsoperation sein. Die Benennung derhasNext()
Methode kann sehr irreführend sein, was tatsächlich vor sich geht.Für ein Beispiel aus der Praxis hat mich dieses Designproblem bei der Implementierung der Rx-APIs (Reactive Extensions) von Microsoft in Java gebissen. Insbesondere für den Iterator, der von erstellt wurde,
IObservable.asIterable()
um ordnungsgemäß zu funktionieren,hasNext()
müsste die Methode blockieren und auf das Eintreffen der nächsten Element- / Fehler- / Abschlussbenachrichtigung warten. Das ist kaum intuitiv: Der Name impliziert eine einfache Zustandsprüfung. Vergleichen Sie dies mit dem C # -Design, beiMoveNext()
dem blockiert werden müsste, was kein völlig unerwartetes Ergebnis ist, wenn Sie mit einem träge ausgewerteten Stream arbeiten.Mein Punkt ist folgender: Das Modell "nächster Iterator" ist dem Modell "dieser Iterator" vorzuziehen, da das Modell "dieser Iterator" häufig eine Lüge ist: Möglicherweise müssen Sie das nächste Element vorab abrufen, um es zu überprüfen der Zustand des Iterators. Ein Design, das diese Möglichkeit klar kommuniziert, ist meiner Meinung nach vorzuziehen. Ja, die Namenskonventionen von Java folgen zwar dem "nächsten" Modell, aber die Auswirkungen auf das Verhalten ähneln denen des "this-iterator" -Modells.
Klarstellung: Vielleicht war es eine schlechte Wahl, Java und C # gegenüberzustellen, weil Javas Modell täuscht. Ich betrachte die wichtigste Unterscheidung in den vom OP vorgestellten Modellen darin, dass das "this-iterator" -Modell die "is valid" -Logik von der Logik "current / next element abrufen" entkoppelt, während es sich um eine ideale "next-iterator" -Implementierung handelt kombiniert diese Operationen. Ich halte es für angebracht, sie zu kombinieren, da in einigen Fällen das Vorabrufen des nächsten Elements erforderlich ist, um festzustellen, ob der Status des Iterators gültig ist , sodass die Möglichkeit so explizit wie möglich gemacht werden sollte.
Ich sehe keinen signifikanten Designunterschied zwischen:
Aber ich sehe hier einen bedeutenden Unterschied:
In den beiden
isValid()
undhasNext()
Beispielen wird die Operation , die eine schnelle und nicht-blockierenden Zustand Prüfung impliziert werden , kann in der Tat eine langsame, Sperrbetrieb . In diesemmoveNext()
Beispiel wird der größte Teil der Arbeit in der erwarteten Methode ausgeführt, unabhängig davon, ob es sich um einen eifrig oder träge bewerteten Stream handelt.quelle
hasNext()
ist es ähnlichisValid()
undnext()
ähnlichcurrent()
.Wenn eine Berechnung erforderlich ist, um jeden Wert zu berechnen, der den nächsten Wert erhalten muss, bevor Sie nach dem ersten Wert fragen, können Sie die Verarbeitung dieses ersten Werts über die Konstruktion des Iterators hinaus verschieben.
Beispielsweise könnte der Iterator eine Abfrage darstellen, die noch nicht ausgeführt wurde. Wenn das erste Element angefordert wird, wird die Datenbank nach den Ergebnissen abgefragt. Unter Verwendung Ihres vorgeschlagenen Entwurfs müsste diese Berechnung entweder beim Aufbau des Iterators (was es schwieriger macht, die Ausführung zu verschieben) oder beim Aufrufen durchgeführt
IsValid
werden. In diesem Fall handelt es sich tatsächlich um eine Fehlbezeichnung, da der AufrufIsValid
tatsächlich den nächsten Wert erhalten würde.quelle
hasNext
nur sein Zustand über den aktuellen Zustand, nicht der zukünftige. Oder nehmen Sie eine andere Ansicht - vergleichen Sie diese mit C ++ - Iteratoren. Sie überprüfen, ob der Status gültig ist,(current!=end)
ohne die tatsächlichen Daten zu berühren.hasNext
Beginn aufrufen , hier wird die Abfrage ausgeführt, Daten werden abgerufen. Im zweiten Modell, das ich aufrufeisValid
, wird hier die Abfrage ausgeführt. Der Punkt der Abfrageausführung ist also der gleiche.IsValid
was Sie tatsächlich tun, gehen Sie und erhalten den nächsten Wert. Dies bedeutet, dass es genau das Gleiche tut wie der vorhandene Iterator (er hat nicht von Anfang an den aktuellen Wert, ohne dass eine Methode aufgerufen werden muss, um ihn abzurufen). Er hat nur einen falsch führenden Namen.