Wo ist die Kombinationsreihenfolge des Sammlers (Sammlers, Akkumulators, Kombinators) definiert?

8

In den Java-API-Dokumentationen heißt es, dass der Parameter combiner der Methode collect wie folgt lauten muss:

  

eine assoziative, nicht störende, zustandslose Funktion zum Kombinieren zweier Werte, die mit der Akkumulatorfunktion kompatibel sein müssen

A combiner ist ein BiConsumer<R,R> , das zwei Parameter vom Typ R empfängt und void zurückgibt. Aber die Dokumentation gibt nicht an, ob wir die Elemente in den ersten oder zweiten Parameter kombinieren sollen?

Zum Beispiel können die folgenden Beispiele unterschiedliche Ergebnisse liefern, abhängig von der Reihenfolge der Kombination: m1.addAll(m2) oder m2.addAll(m1) .

%Vor%

Ich weiß, dass wir in diesem Fall einfach ein Methoden-Handle verwenden könnten, beispielsweise ArrayList::addAll . Es gibt jedoch einige Fälle, in denen ein Lambda benötigt wird und die Elemente in der richtigen Reihenfolge kombiniert werden müssen. Andernfalls könnten wir bei der parallelen Verarbeitung ein inkonsistentes Ergebnis erhalten.

Wird dies in irgendeinem Teil der Java 8 API-Dokumentation beansprucht? Oder ist es wirklich egal?

    
Miguel Gamboa 29.05.2015, 09:44
quelle

2 Antworten

6

Scheint, dass dies nicht explizit in der Dokumentation angegeben ist. Allerdings gibt es ein Konzept in der Stream-API . Stream kann entweder bestellt werden oder nicht. Es kann von Anfang an ungeordnet sein, wenn der Quell-Spliterator ungeordnet ist (z. B. wenn die Stream-Quelle HashSet ist). Oder der Stream wird möglicherweise ungeordnet, wenn der Benutzer explizit unordered() operation verwendet hat. Wenn der Stream geordnet ist, sollte auch der Sammelprozedur stabil sein, daher wird angenommen, dass angenommen ist, dass für bestellte Streams der combiner die Argumente in der strikten Reihenfolge erhält. Es ist jedoch nicht für einen ungeordneten Stream garantiert.

    
Tagir Valeev 29.05.2015, 10:09
quelle
9

Natürlich ist es wichtig, wenn Sie m2.addAll(m1) anstelle von m1.addAll(m2) verwenden, ändert sich nicht nur die Reihenfolge der Elemente, sondern die Operation wird komplett abgebrochen. Da ein BiConsumer kein Ergebnis liefert, haben Sie keine Kontrolle darüber, welches Objekt der Aufrufer als Ergebnis verwenden wird, und da der Aufrufer das erste verwendet, führt das Ändern des zweiten stattdessen zu Datenverlust.

Es gibt einen Hinweis, wenn Sie sich die Funktion Akkumulator ansehen, die den Typ BiConsumer<R,? super T> hat, also nichts anderes tun kann, als das Element vom Typ T als bereitgestellt zu speichern zweites Argument in den Container vom Typ R als erstes Argument.

Wenn Sie sich die Dokumentation von Collector , die eine Funktion BinaryOperator als Kombinator verwendet, damit der Kombinierer entscheiden kann, welches Argument zurückgegeben werden soll (oder sogar eine völlig andere Ergebnisinstanz), finden Sie:

  

Die Assoziativitätsbedingung sagt aus, dass das Teilen der Berechnung ein äquivalentes Ergebnis erzeugen muss. Das heißt, für alle Eingabeelemente t1 und t2 müssen die Ergebnisse r1 und r2 in der folgenden Berechnung gleichwertig sein:

%Vor%

Wenn wir also annehmen, dass der Akkumulator in der Reihenfolge der Aufeinandertreffen angewendet wird, muss der Kombinierer das erste und das zweite Argument in der Reihenfolge von links nach rechts kombinieren, um ein gleichwertiges Ergebnis.

Jetzt hat die Drei-Arg-Version von Stream.collect eine etwas andere Signatur, indem sie ein BiConsumer als Kombinator genau zur Unterstützung von Methodenreferenzen wie ArrayList::addAll . Unter der Annahme einer Konsistenz während all dieser Operationen und unter Berücksichtigung des Zwecks dieser Signaturänderung können wir sicher annehmen, dass es das erste Argument sein muss, das der zu modifizierende Container ist.

Aber es scheint, dass dies eine späte Änderung ist und die Dokumentation nicht entsprechend angepasst wurde. Wenn Sie sich den Abschnitt Änderbare Reduktion ansehen der Paketdokumentation werden Sie feststellen, dass sie so angepasst wurde, dass sie die tatsächlichen Stream.collect Signatur- und Verwendungsbeispiele zeigt, aber exakt die gleiche Definition bezüglich der Assoziativitätsbeschränkung wie oben gezeigt wiederholt, trotz der Tatsache, dass finisher.apply(combiner.apply(a2, a3)) doesn ' t funktionieren, wenn combiner ein BiConsumer ...

ist

Das Dokumentationsproblem wurde als JDK-8164691 gemeldet und in Java 9. adressiert. Die neue Dokumentation lautet:

  

combiner - eine assoziative, nicht störende, zustandslose Funktion, die zwei Partial-Ergebnis-Container akzeptiert und zusammenführt, die mit der Akkumulator-Funktion kompatibel sein müssen. Die Combiner-Funktion muss die Elemente aus dem zweiten Ergebniscontainer in den ersten Ergebniscontainer falten.

    
Holger 29.05.2015 13:13
quelle

Tags und Links