Wann würden Sie collect()
vs verwenden reduce()
? Hat jemand gute, konkrete Beispiele dafür, wann es definitiv besser ist, in die eine oder andere Richtung zu gehen?
Javadoc erwähnt, dass collect () eine veränderliche Reduktion ist .
Da es sich um eine veränderbare Reduzierung handelt, gehe ich davon aus, dass eine Synchronisierung (intern) erforderlich ist, was sich wiederum nachteilig auf die Leistung auswirken kann. Vermutlich reduce()
ist es leichter parallelisierbar, wenn nach jedem Schritt der Reduzierung eine neue Datenstruktur für die Rückgabe erstellt werden muss.
Die obigen Aussagen sind jedoch Vermutungen und ich würde gerne einen Experten hier einschalten.
java
java-8
java-stream
jimhooker2002
quelle
quelle
Antworten:
reduce
ist eine " Fold " -Operation und wendet einen binären Operator auf jedes Element im Stream an, wobei das erste Argument für den Operator der Rückgabewert der vorherigen Anwendung und das zweite Argument das aktuelle Stream-Element ist.collect
ist eine Aggregationsoperation, bei der eine "Sammlung" erstellt und jedes Element zu dieser Sammlung "hinzugefügt" wird. Sammlungen in verschiedenen Teilen des Streams werden dann zusammenaddiert.Das von Ihnen verknüpfte Dokument gibt den Grund für zwei unterschiedliche Ansätze an:
Der Punkt ist also, dass die Parallelisierung in beiden Fällen gleich ist, aber in dem
reduce
Fall wenden wir die Funktion auf die Stream-Elemente selbst an. In diesemcollect
Fall wenden wir die Funktion auf einen veränderlichen Container an.quelle
int
ist unveränderlich, sodass Sie einen Erfassungsvorgang nicht ohne weiteres verwenden können. Sie könnten einen schmutzigen Hack wie einenAtomicInteger
oder einen Brauch machen,IntWrapper
aber warum sollten Sie? Eine Falzoperation unterscheidet sich einfach von einer Sammeloperation.reduce
Methode, mit der Sie Objekte vom Typ zurückgeben können, die sich von Elementen des Streams unterscheiden.Der Grund ist einfach:
collect()
kann nur funktionieren , mit wandelbaren Ergebnisobjekten.reduce()
wurde entwickelt, um mit unveränderlichen Ergebnisobjekten zu arbeiten ."
reduce()
mit unveränderlichem" BeispielBeispiel "
collect()
mit veränderlichem"Beispiel : Wenn Sie möchten , dass manuell eine Summe berechnen unter Verwendung von
collect()
mit er kann nicht arbeiten ,BigDecimal
sondern nur mitMutableInt
vonorg.apache.commons.lang.mutable
zum Beispiel. Sehen:Dies funktioniert, weil der Akkumulator
container.add(employee.getSalary().intValue());
kein neues Objekt mit dem Ergebnis zurückgeben soll, sondern den Status des veränderlichencontainer
Typs ändern sollMutableInt
.Wenn Sie
BigDecimal
stattdessen für diecontainer
verwenden möchten, können Sie diecollect()
Methode nicht verwenden , dacontainer.add(employee.getSalary());
dies die nicht ändern würde,container
daBigDecimal
sie unveränderlich ist. (Abgesehen davonBigDecimal::new
würde nicht funktionieren, daBigDecimal
kein leerer Konstruktor vorhanden ist)quelle
Integer
Konstruktor (new Integer(6)
) verwenden, der in späteren Java-Versionen veraltet ist.Integer.valueOf(6)
StringBuilder
was veränderlich ist. Siehe: hg.openjdk.java.net/jdk8/jdk8/jdk/file/687fd7c7986d/src/share/…Die normale Reduzierung soll zwei unveränderliche Werte wie int, double usw. kombinieren und einen neuen erzeugen. Es ist eine unveränderliche Reduzierung. Im Gegensatz dazu dient die Sammelmethode dazu, einen Container zu mutieren , um das Ergebnis zu akkumulieren, das er erzeugen soll.
Nehmen wir an, Sie möchten zur Veranschaulichung des Problems
Collectors.toList()
eine einfache Reduzierung wie erreichenDies entspricht
Collectors.toList()
. In diesem Fall mutieren Sie jedoch dieList<Integer>
. Wie wir wissen,ArrayList
ist das weder threadsicher noch sicher, während der Iteration Werte hinzuzufügen / daraus zu entfernen, sodass Sie entweder eine gleichzeitige AusnahmeArrayIndexOutOfBoundsException
oder eine andere Ausnahme (insbesondere bei paralleler Ausführung) erhalten, wenn Sie die Liste oder den Kombinierer aktualisieren versucht, die Listen zusammenzuführen, da Sie die Liste mutieren, indem Sie die Ganzzahlen akkumulieren (hinzufügen). Wenn Sie diesen Thread sicher machen möchten, müssen Sie jedes Mal eine neue Liste übergeben, die die Leistung beeinträchtigen würde.Im Gegensatz dazu
Collectors.toList()
funktioniert das in ähnlicher Weise. Es garantiert jedoch die Thread-Sicherheit, wenn Sie die Werte in der Liste akkumulieren. Aus der Dokumentation zurcollect
Methode :Um Ihre Frage zu beantworten:
Wenn Sie unveränderliche Werte wie
ints
, habendoubles
,Strings
funktioniert die normale Reduzierung einwandfrei. Wenn Sie jedochreduce
Ihre Werte in eineList
(veränderbare Datenstruktur) umwandeln müssen, müssen Sie mit dercollect
Methode eine veränderbare Reduktion verwenden.quelle
x
Threads starten können , die jeweils "zur Identität hinzufügen" und dann miteinander kombiniert werden. Gutes Beispiel.public static void main(String[] args) { List<Integer> l = new ArrayList<>(); l.add(1); l.add(10); l.add(3); l.add(-3); l.add(-4); List<Integer> numbers = l.stream().reduce( new ArrayList<Integer>(), (List<Integer> l2, Integer e) -> { l2.add(e); return l2; }, (List<Integer> l1, List<Integer> l2) -> { l1.addAll(l2); return l1; });for(Integer i:numbers)System.out.println(i); } }
Ich habe versucht und keine CCm-Ausnahme bekommenDer Strom sei a <- b <- c <- d
In Reduktion,
Sie haben ((a # b) # c) # d
wo # ist diese interessante Operation, die Sie gerne machen würden.
In der Sammlung,
Ihr Sammler wird eine Art Sammelstruktur K haben.
K verbraucht a. K verbraucht dann b. K verbraucht dann c. K verbraucht dann d.
Am Ende fragen Sie K, was das Endergebnis ist.
K gibt es dir dann.
quelle
Sie unterscheiden sich stark im potenziellen Speicherbedarf zur Laufzeit. Während Sie alle Daten
collect()
sammeln und in die Sammlung aufnehmen, werden Sie ausdrücklich aufgefordert, anzugeben, wie die Daten reduziert werden sollen, die den Stream durchlaufen haben.reduce()
Wenn Sie beispielsweise einige Daten aus einer Datei lesen, verarbeiten und in eine Datenbank einfügen möchten, erhalten Sie möglicherweise einen ähnlichen Java-Stream-Code:
In diesem Fall wird
collect()
Java verwendet, um Daten zu streamen und das Ergebnis in der Datenbank zu speichern. Ohnecollect()
die Daten wird nie gelesen und nie gespeichert.Dieser Code generiert gerne einen
java.lang.OutOfMemoryError: Java heap space
Laufzeitfehler, wenn die Dateigröße groß genug oder die Heap-Größe niedrig genug ist. Der offensichtliche Grund ist, dass versucht wird, alle Daten, die es durch den Stream geschafft haben (und tatsächlich bereits in der Datenbank gespeichert wurden), in die resultierende Sammlung zu stapeln, was den Heap in die Luft sprengt.Wenn Sie jedoch ersetzen
collect()
mitreduce()
- es wird kein Problem mehr sein , da letzteres reduziert und all verwirft die Daten , die sie durch gemacht.Im vorgestellten Beispiel ersetzen Sie einfach
collect()
etwas durchreduce
:Sie müssen sich nicht einmal darum kümmern, dass die Berechnung von der abhängig ist,
result
da Java keine reine FP-Sprache (Functional Programming) ist und die Daten, die am Ende des Streams nicht verwendet werden, aufgrund möglicher Nebenwirkungen nicht optimieren kann .quelle
System.out.println (Summe);
Die Reduktionsfunktion behandelt zwei Parameter, der erste Parameter ist der vorherige Rückgabewert im Stream, der zweite Parameter ist der aktuelle Berechnungswert im Stream, er summiert den ersten Wert und den aktuellen Wert als ersten Wert in der nächsten Berechnung.
quelle
Laut den Dokumenten
Grundsätzlich würden Sie es also
reducing()
nur verwenden, wenn Sie innerhalb eines Sammels gezwungen werden. Hier ist ein weiteres Beispiel :Nach diesem Tutorial ist Reduzieren manchmal weniger effizient
Daher wird die Identität in einem reduzierten Szenario "wiederverwendet", sodass sie nach
.reduce
Möglichkeit etwas effizienter ist .quelle