Gemäß der Dokumentation auf der Oracle-Website [...]
Dieser Link ist für Java 8. Möglicherweise möchten Sie die Dokumentation für Java 9 (veröffentlicht im Jahr 2017) und spätere Versionen lesen, da diese diesbezüglich expliziter sind. Speziell:
Eine Stream-Implementierung lässt einen erheblichen Spielraum bei der Optimierung der Berechnung des Ergebnisses. Beispielsweise kann eine Stream-Implementierung Operationen (oder ganze Stufen) aus einer Stream-Pipeline entfernen - und daher den Aufruf von Verhaltensparametern eliminieren -, wenn nachgewiesen werden kann, dass dies das Ergebnis der Berechnung nicht beeinflusst. Dies bedeutet, dass Nebenwirkungen von Verhaltensparametern möglicherweise nicht immer ausgeführt werden und nicht berücksichtigt werden sollten, sofern nicht anders angegeben (z. B. durch die Terminaloperationen forEach
und forEachOrdered
). (Ein spezielles Beispiel für eine solche Optimierung finden Sie in der über den count()
Vorgang dokumentierten API-Anmerkung . Weitere Informationen finden Sie im Abschnitt zu Nebenwirkungen der Dokumentation zum Stream-Paket.)
Quelle: Java 9's Javadoc für die Stream
Schnittstelle .
Und auch die aktualisierte Version des von Ihnen zitierten Dokuments:
Nebenwirkungen
Nebenwirkungen von Verhaltensparametern bei Stream-Vorgängen werden im Allgemeinen nicht empfohlen, da sie häufig zu unwissentlichen Verstößen gegen die Anforderung der Staatenlosigkeit sowie zu anderen Sicherheitsrisiken für Threads führen können.
Wenn die Verhaltensparameter Nebenwirkungen haben, sofern nicht ausdrücklich angegeben, gibt es keine Garantie für :
- die Sichtbarkeit dieser Nebenwirkungen für andere Themen;
- dass verschiedene Operationen an dem "gleichen" Element innerhalb derselben Stream-Pipeline in demselben Thread ausgeführt werden; und
- Diese Verhaltensparameter werden immer aufgerufen, da eine Stream-Implementierung Operationen (oder ganze Stufen) aus einer Stream-Pipeline entfernen kann, wenn nachgewiesen werden kann, dass dies das Ergebnis der Berechnung nicht beeinflusst.
Die Reihenfolge der Nebenwirkungen kann überraschend sein. Selbst wenn eine Pipeline gezwungen ist, ein Ergebnis zu erzeugen, das mit der Reihenfolge der Begegnung der Stream-Quelle übereinstimmt (z. B. IntStream.range(0,5).parallel().map(x -> x*2).toArray()
muss erzeugt werden [0, 2, 4, 6, 8]
), werden keine Garantien für die Reihenfolge gegeben, in der die Mapper-Funktion auf einzelne Elemente angewendet wird, oder in Welcher Thread ein Verhaltensparameter für ein bestimmtes Element ausgeführt wird.
Das Eliminieren von Nebenwirkungen kann ebenfalls überraschend sein. Mit Ausnahme von Terminaloperationen forEach
undforEachOrdered
können Nebenwirkungen von Verhaltensparametern nicht immer ausgeführt werden, wenn die Stream-Implementierung die Ausführung von Verhaltensparametern optimieren kann, ohne das Ergebnis der Berechnung zu beeinflussen. (Ein spezielles Beispiel finden Sie in der API-Anmerkung, die für den count
Vorgang dokumentiert ist .)
Quelle: Java 9's Javadoc für das java.util.stream
Paket .
Alle Betonung von mir.
Wie Sie sehen können, wird in der aktuellen offiziellen Dokumentation detaillierter auf die Probleme eingegangen, die auftreten können, wenn Sie sich entscheiden, Nebenwirkungen in Ihren Stream-Vorgängen zu verwenden. Es ist auch ganz klar auf forEach
und forEachOrdered
die einzigen Terminalbetrieb zu sein , wo die Ausführung von Nebenwirkungen garantiert wird (wohlgemerkt, fadenSicherheitsFragen immer noch gelten, wie die offiziellen Beispiele zeigen).
Davon abgesehen und in Bezug auf Ihren spezifischen Code und nur diesen Code:
public List<SavedCars> saveCars(List<Car> cars) {
return cars.stream()
.map(this::saveCar)
.collect(Collectors.toList());
}
Ich sehe keine Streams-bezogenen Probleme mit dem Code wie er ist.
- Der
.map()
Schritt wird ausgeführt, weil .collect()
(eine veränderbare Reduktionsoperation , die das offizielle Dokument anstelle von Dingen empfiehlt .forEach(list::add)
) auf .map()
der Ausgabe beruht und da diese Ausgabe (dh saveCar()
die Ausgabe) sich von ihrer Eingabe unterscheidet, kann der Stream nicht "beweisen" dass [eliding] es das Ergebnis der Berechnung nicht beeinflussen würde " .
- Es ist nicht
parallelStream()
so, dass es keine Parallelitätsprobleme verursachen sollte, die zuvor nicht existierten (natürlich .parallel()
kann es zu Problemen kommen , wenn jemand später eine hinzufügt - ähnlich wie wenn jemand beschlossen hat, eine for
Schleife zu parallelisieren , indem er neue Threads für die inneren Berechnungen startet ).
Das bedeutet nicht, dass der Code in diesem Beispiel Good Code ™ ist. Die Sequenz .stream.map(::someSideEffect()).collect()
als Möglichkeit, Nebenwirkungen für jedes Element in einer Sammlung auszuführen, sieht möglicherweise einfacher / kurzer / eleganter aus. als sein for
Gegenstück, und es kann manchmal sein. Wie Eugene, Holger und einige andere Ihnen sagten, gibt es jedoch bessere Möglichkeiten, dies zu erreichen.
Ein kurzer Gedanke: Die Kosten für das Stream
Starten eines einfachen for
oder das Iterieren eines einfachen sind nicht zu vernachlässigen, es sei denn, Sie haben viele Elemente, und wenn Sie viele Elemente haben, möchten Sie: a) wahrscheinlich keinen neuen DB-Zugriff vornehmen für jeden saveAll(List items)
wäre also eine API besser; und b) wahrscheinlich nicht den Leistungseinbruch der Verarbeitung viel ertragen wollen von Elementen nacheinander, so dass Sie am Ende die Parallelisierung verwenden und dann eine ganze Reihe neuer Probleme auftreten.