Angenommen, ich habe eine Methode, die eine schreibgeschützte Ansicht in eine Mitgliederliste zurückgibt:
class Team {
private List < Player > players = new ArrayList < > ();
// ...
public List < Player > getPlayers() {
return Collections.unmodifiableList(players);
}
}
Angenommen, der Client durchläuft die Liste nur einmal und sofort. Vielleicht, um die Spieler in eine JList zu setzen oder so. Der Kunde ist nicht speichert einen Verweis auf die Liste für eine spätere Inspektion!
Sollte ich in diesem allgemeinen Szenario stattdessen einen Stream zurückgeben?
public Stream < Player > getPlayers() {
return players.stream();
}
Oder ist die Rückgabe eines Streams in Java nicht idiomatisch? Wurden Streams so konzipiert, dass sie immer innerhalb desselben Ausdrucks "beendet" werden, in dem sie erstellt wurden?
java
collections
java-8
encapsulation
java-stream
Fredoverflow
quelle
quelle
players.stream()
ist genau eine solche Methode, die einen Stream an den Aufrufer zurückgibt. Die eigentliche Frage ist, möchten Sie den Anrufer wirklich auf eine einzelne Durchquerung beschränken und ihm auch den Zugriff auf Ihre Sammlung über dieCollection
API verweigern ? Vielleicht möchte der Anrufer es einfach zuaddAll
einer anderen Sammlung?Antworten:
Die Antwort lautet wie immer "es kommt darauf an". Dies hängt davon ab, wie groß die zurückgegebene Sammlung sein wird. Dies hängt davon ab, ob sich das Ergebnis im Laufe der Zeit ändert und wie wichtig die Konsistenz des zurückgegebenen Ergebnisses ist. Und es hängt sehr davon ab, wie der Benutzer die Antwort wahrscheinlich verwendet.
Beachten Sie zunächst, dass Sie immer eine Sammlung aus einem Stream abrufen können und umgekehrt:
Die Frage ist also, was für Ihre Anrufer nützlicher ist.
Wenn Ihr Ergebnis unendlich sein könnte, gibt es nur eine Wahl: Stream.
Wenn Ihr Ergebnis sehr groß sein könnte, bevorzugen Sie wahrscheinlich Stream, da es möglicherweise keinen Wert hat, alles auf einmal zu materialisieren, und dies könnte einen erheblichen Heap-Druck erzeugen.
Wenn der Aufrufer nur iterieren möchte (suchen, filtern, aggregieren), sollten Sie Stream bevorzugen, da Stream diese bereits integriert hat und keine Sammlung materialisiert werden muss (insbesondere, wenn der Benutzer die Sammlung möglicherweise nicht verarbeitet) ganzes Ergebnis.) Dies ist ein sehr häufiger Fall.
Selbst wenn Sie wissen, dass der Benutzer es mehrmals iterieren oder auf andere Weise beibehalten wird, möchten Sie möglicherweise stattdessen einen Stream zurückgeben, da die Sammlung, in die Sie ihn einfügen (z. B. ArrayList), möglicherweise nicht die ist Formular, das sie wollen, und dann muss der Anrufer es trotzdem kopieren. Wenn Sie einen Stream zurückgeben, können
collect(toCollection(factory))
sie ihn in genau der gewünschten Form abrufen.Die oben genannten Fälle "Stream bevorzugen" ergeben sich hauptsächlich aus der Tatsache, dass Stream flexibler ist. Sie können sich spät daran binden, wie Sie es verwenden, ohne die Kosten und Einschränkungen für die Materialisierung in einer Sammlung zu verursachen.
Der einzige Fall, in dem Sie eine Sammlung zurückgeben müssen, besteht darin, dass hohe Konsistenzanforderungen bestehen und Sie einen konsistenten Schnappschuss eines sich bewegenden Ziels erstellen müssen. Dann möchten Sie die Elemente in eine Sammlung einfügen, die sich nicht ändert.
Daher würde ich sagen, dass Stream die meiste Zeit die richtige Antwort ist - es ist flexibler, verursacht normalerweise keine unnötigen Materialisierungskosten und kann bei Bedarf problemlos in die Sammlung Ihrer Wahl umgewandelt werden. Manchmal müssen Sie jedoch eine Sammlung zurückgeben (z. B. aufgrund starker Konsistenzanforderungen), oder Sie möchten die Sammlung zurückgeben, weil Sie wissen, wie der Benutzer sie verwenden wird, und wissen, dass dies für ihn am bequemsten ist.
quelle
Ich habe ein paar Punkte zu Brian Goetz 'hervorragender Antwort hinzuzufügen .
Es ist durchaus üblich, einen Stream von einem Methodenaufruf im "Getter" -Stil zurückzugeben. Besuchen Sie die Stream-Verwendungsseite im Java 8-Javadoc und suchen Sie nach "Methoden ..., die Stream zurückgeben" für andere Pakete als
java.util.Stream
. Diese Methoden beziehen sich normalerweise auf Klassen, die mehrere Werte oder Aggregationen von etwas darstellen oder enthalten können. In solchen Fällen haben APIs normalerweise Sammlungen oder Arrays davon zurückgegeben. Aus all den Gründen, die Brian in seiner Antwort erwähnt hat, ist es sehr flexibel, hier Methoden zur Rückgabe von Streams hinzuzufügen. Viele dieser Klassen verfügen bereits über Methoden zur Rückgabe von Sammlungen oder Arrays, da die Klassen älter sind als die Streams-API. Wenn Sie eine neue API entwerfen und es sinnvoll ist, Stream-Rückgabemethoden bereitzustellen, ist es möglicherweise nicht erforderlich, auch Sammlungsrückgabemethoden hinzuzufügen.Brian erwähnte die Kosten für die "Materialisierung" der Werte in einer Sammlung. Um diesen Punkt zu verdeutlichen, fallen hier tatsächlich zwei Kosten an: die Kosten für das Speichern von Werten in der Sammlung (Speicherzuweisung und Kopieren) und auch die Kosten für das erstmalige Erstellen der Werte. Die letztgenannten Kosten können häufig reduziert oder vermieden werden, indem das faulheitssuchende Verhalten eines Streams ausgenutzt wird. Ein gutes Beispiel hierfür sind die APIs in
java.nio.file.Files
:Es muss nicht nur
readAllLines
den gesamten Dateiinhalt gespeichert werden, um ihn in der Ergebnisliste zu speichern, sondern die Datei muss auch bis zum Ende gelesen werden, bevor die Liste zurückgegeben wird. Dielines
Methode kann fast sofort nach dem Einrichten zurückgegeben werden, sodass das Lesen von Dateien und Zeilenumbrüche erst später erfolgen, wenn dies erforderlich ist - oder überhaupt nicht. Dies ist ein großer Vorteil, wenn sich der Anrufer beispielsweise nur für die ersten zehn Zeilen interessiert:Natürlich kann beträchtlicher Speicherplatz gespart werden, wenn der Anrufer den Stream filtert, um nur Zeilen zurückzugeben, die einem Muster usw. entsprechen.
Eine Redewendung, die sich abzuzeichnen scheint, besteht darin, Stream-Return-Methoden nach dem Plural des Namens der Dinge zu benennen, die sie darstellen oder enthalten, ohne
get
Präfix. Auch wennstream()
es ein vernünftiger Name für eine Stream-Rückgabemethode ist, wenn nur ein möglicher Satz von Werten zurückgegeben werden kann, gibt es manchmal Klassen mit Aggregationen mehrerer Wertetypen. Angenommen, Sie haben ein Objekt, das sowohl Attribute als auch Elemente enthält. Sie können zwei Stream-Return-APIs bereitstellen:quelle
So werden sie in den meisten Beispielen verwendet.
Hinweis: Die Rückgabe eines Streams unterscheidet sich nicht wesentlich von der Rückgabe eines Iterators (zugegeben mit viel mehr Ausdruckskraft).
IMHO ist die beste Lösung, zu beschreiben, warum Sie dies tun, und die Sammlung nicht zurückzugeben.
z.B
oder wenn Sie sie zählen wollen
quelle
Wenn der Stream endlich ist und für die zurückgegebenen Objekte eine erwartete / normale Operation ausgeführt wird, die eine aktivierte Ausnahme auslöst, gebe ich immer eine Sammlung zurück. Denn wenn Sie an jedem der Objekte etwas tun, das eine Prüfausnahme auslösen kann, hassen Sie den Stream. Ein wirklicher Mangel an Streams ist die Unfähigkeit, geprüfte Ausnahmen elegant zu behandeln.
Vielleicht ist das ein Zeichen dafür, dass Sie die überprüften Ausnahmen nicht benötigen, was fair ist, aber manchmal sind sie unvermeidlich.
quelle
Im Gegensatz zu Sammlungen weisen Streams zusätzliche Merkmale auf . Ein Stream, der von einer beliebigen Methode zurückgegeben wird, kann sein:
Diese Unterschiede bestehen auch in Sammlungen, aber dort sind sie Teil des offensichtlichen Vertrags:
Als Konsument eines Streams (entweder aus einer Methodenrückgabe oder als Methodenparameter) ist dies eine gefährliche und verwirrende Situation. Um sicherzustellen, dass sich ihr Algorithmus korrekt verhält, müssen Verbraucher von Streams sicherstellen, dass der Algorithmus keine falschen Annahmen über die Stream-Eigenschaften macht. Und das ist sehr schwer zu tun. Beim Unit-Test würde dies bedeuten, dass Sie alle Ihre Tests multiplizieren müssen, um sie mit demselben Stream-Inhalt zu wiederholen, jedoch mit Streams, die es sind
Das Schreiben von Methodenschutz für Streams , die eine IllegalArgumentException auslösen, wenn der Eingabestream Merkmale aufweist, die Ihren Algorithmus beschädigen, ist schwierig, da die Eigenschaften ausgeblendet sind.
Damit bleibt Stream nur dann eine gültige Wahl in einer Methodensignatur, wenn keines der oben genannten Probleme von Bedeutung ist, was selten der Fall ist.
Es ist viel sicherer, andere Datentypen in Methodensignaturen mit einem expliziten Vertrag (und ohne implizite Thread-Pool-Verarbeitung) zu verwenden, der es unmöglich macht, versehentlich Daten mit falschen Annahmen über Reihenfolge, Größe oder Parallelität (und Threadpool-Verwendung) zu verarbeiten.
quelle
Ich denke, es hängt von Ihrem Szenario ab.
Team
MöglicherweiseIterable<Player>
ist es ausreichend , wenn Sie Ihr Gerät herstellen .oder im a funktionalen Stil:
Wenn Sie jedoch eine vollständigere und flüssigere API wünschen, könnte ein Stream eine gute Lösung sein.
quelle
stream.count()
ist auch ziemlich einfach), ist diese Zahl für nichts anderes als das Debuggen oder Schätzen wirklich sehr bedeutsam.Während einige der bekannteren Befragten großartige allgemeine Ratschläge gaben, bin ich überrascht, dass niemand genau gesagt hat:
Wenn Sie bereits ein "materialisiertes"
Collection
in der Hand haben (dh es wurde bereits vor dem Aufruf erstellt - wie im angegebenen Beispiel, wo es sich um ein Mitgliedsfeld handelt), macht es keinen Sinn, es in ein zu konvertierenStream
. Der Anrufer kann dies problemlos selbst tun. Wenn der Anrufer die Daten in ihrer ursprünglichen Form verwenden möchte, werden Sie durch Konvertieren in Daten gezwungen,Stream
redundante Arbeiten auszuführen, um eine Kopie der ursprünglichen Struktur wiederherzustellen.quelle
quelle
Ich hätte wahrscheinlich zwei Methoden, eine, um a zurückzugeben,
Collection
und eine, um die Sammlung als zurückzugebenStream
.Dies ist das Beste aus beiden Welten. Der Client kann wählen, ob er die Liste oder den Stream haben möchte, und er muss nicht die zusätzliche Objekterstellung durchführen, um eine unveränderliche Kopie der Liste zu erstellen, nur um einen Stream zu erhalten.
Dadurch wird Ihrer API nur eine weitere Methode hinzugefügt, sodass Sie nicht zu viele Methoden haben
quelle