Ist es eine vernünftige Sache, Streams überall dort zurückzugeben, wo wir normalerweise Sammlungen zurückgeben würden?

19

Während ich meine API entwickle, die an keinen Legacy-Code gebunden ist, schreibe ich häufig Methoden, bei denen es sich um reine Streams-Pipeline handelt, die durch das Sammeln der Ergebnisse beendet wird. Wie dieser:

ImmutableSet<T> deriveSomethingMeaningfulFromPrivateState() {
    return myPrivateThingies.stream()
        .map(this::ownerOfThing)
        .map(Owner::socialStatus)
        .filter(SocialStatus::isHeAFineMatey)
        .collect(MyCustomCollectors.toImmutableSet());
}

Die meisten Clients dieser Klasse benötigen normalerweise die Auflistung (in diesem Fall ein ImmutableSet), um Elemente zu durchsuchen und darüber zu iterieren. Einige Clients können jedoch von einem Stream profitieren, sodass sie darüber hinaus noch weitere Operationen weiterleiten können Stream, ohne dass ein neuer Stream aus der Sammlung abgerufen werden muss. Wenn Sie also einen Stream zurückgeben, erhalten die Kunden eine Reihe von Optionen, die sie hätten, wenn sie nur die Sammlung hätten (schließlich können sie collect()den Stream immer selbst:

Stream<T> deriveSomethingMeaningfulFromPrivateState() {
    return myPrivateThingies.stream()
        .map(this::ownerOfthing)
        .map(Owner::socialStatus)
        .filter(SocialStatus::isHeAFineMatey);
        // No collect
}

Dieser Ansatz ist für mich verlockend, es auszuprobieren, da ich keine potenziellen Fehler sehe, die es haben könnte. Ich habe diesen Ansatz jedoch noch nie in einer Bibliothek gesehen (wahrscheinlich, weil nach dem Erscheinen von Java 8 nicht mehr viele Bibliotheken veröffentlicht wurden), daher habe ich etwas Angst, ihn zu übernehmen. Bestehende Bibliotheksklassen geben normalerweise Sammlungen zurück, wenn sie etwas vom privaten Zustand ableiten.

Gibt es etwas Schlimmes, das passieren könnte, wenn ich mich entscheide, einen Stream zurückzugeben, wo immer mein Java-8-Vorgänger eine Collection zurückgeben würde? Oder mache ich hier wahrscheinlich ein Gegenmuster mit all dem, was aus dem Privatstaat stammt?

jojman
quelle

Antworten:

14

Wenn myPrivateThingieses veränderlich ist, haben Sie eine versteckte Abhängigkeit zwischen Ihrem privaten Status und den Stream-Ergebnissen erstellt. Wenn es dem Kunden möglich ist, indirekt einen myPrivateThingiesZustandswechsel auszulösen, wird er beim Anruf ein anderes Ergebnis erhalten als das, collectdas Sie ursprünglich herausgeben wollten.

Wenn myPrivateThingiesunveränderlich ist, ist das Ergebnis referenziell transparent, aber es gibt noch ein weiteres Problem, auf das Sie achten müssen: semantischen Müll , dh das Festhalten an großen Mengen an Speicher, die nicht mehr benötigt werden. Angenommen, es myPrivateThingiesist sehr groß und das Ergebnis des Sammelns des Streams ist gering. Der Client streambehält den Stream möglicherweise noch lange bei, nachdem er alle Verweise auf das Objekt, myPrivateThingiesvon dem er stammt, verworfen hat. Dies verhindert jedoch weiterhin, dass Müll gesammelt wird. Das eifrige Sammeln der Ergebnisse würde es ermöglichen myPrivateThingies, befreit zu werden.

Dies geschah tatsächlich vor Java 7 beim Aufruf substring. Oracle entschied, dass die potenzielle Effizienzersparnis, wenn der Teilstring nicht jedes Mal kopiert wird, es nicht wert ist, den durchschnittlichen Benutzer gelegentlich mit einem übermäßigen Speicherverbrauch zu überraschen. Das heißt nicht, dass es keine wirklichen Anwendungsfälle für das alte Verhalten gab (z. B. Parser), aber das eifrige Sammeln der Ergebnisse ist oft schnell genug, und wenn dies passiert, haben Sie keine Vor- und Nachteile.

Durch das Zurückgeben eines Streams kann der Client hingegen auswählen, welche Datenstruktur er für die Speicherung der Ergebnisse verwenden möchte, anstatt dass Sie eine für ihn auswählen. Möglicherweise lohnt es sich, beide Optionen anzubieten.

Doval
quelle
4

Das Wichtigste, das Sie berücksichtigen sollten: Streams kann nur einmal wiederholt werden, während Sie mehr Flexibilität haben als a Collection: Sie können weiterhin mehr Streams erstellen oder sogar Iteratorzusätzliche, sich wiederholende Verarbeitungsschritte für die Ergebnisse ausführen.

Wenn Sie nicht sicher sind, ob Aufrufer der Methode die Ergebnisse einmal und nur einmal verwenden, ist es besser, a zurückzugeben Collection.


Ihr Beispielcode weist einen offensichtlichen Fehler auf: Warum sollte a SocialStatusdas Konzept einer Person haben he?

hjk
quelle
3

Meiner Ansicht nach nein. Die Dinge, die Sie mit Streams tun können, sind eine strenge Obermenge der Dinge, die Sie mit Sammlungen tun können, und oft können sie effizienter gestaltet werden, sodass es keinen Grund gibt, sie nicht zu verwenden, außer Unbekanntem. "Lambda-Ausdrücke sind die Gateway-Droge zu Java 8, aber Streams sind die wahre Sucht." (Venkat Subramaniam, Funktionale Programmierung in Java )

Kilian Foth
quelle