2015-05-29 2 views
8

Die Java API Dokumentationen besagt, dass die combiner Parameter des collect Methode sein muss:Wo ist die Kombinationsreihenfolge des Sammlers (Sammlers, Akkumulators, Kombinators) definiert?

ein assoziativer, nicht störenden, zustandslose Funktion zwei Werte für die Kombination, die mit Akkumulatorfunktion

kompatibel sein müssen

Eine combiner ist eine BiConsumer<R,R>, die 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? Beispielsweise können die folgenden Beispiele unterschiedliche Ergebnisse ergeben, abhängig von der Reihenfolge der Kombination: m1.addAll(m2) oder m2.addAll(m1).

List<String> res = LongStream 
    .rangeClosed(1, 1_000_000) 
    .parallel() 
    .mapToObj(n -> "" + n) 
    .collect(ArrayList::new, ArrayList::add,(m1, m2) -> m1.addAll(m2)); 

Ich weiß, in diesem Fall, dass wir einfach ein Verfahren handelt, wie ArrayList::addAll nutzen könnten. 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?

+2

Sie haben Recht, das Javadoc ist nicht sehr klar geschrieben - verwirrend, sagt es, dass Combiner assoziativ sein muss !. Es ist wahrscheinlich ein Rest von einer früheren Version, die einen 'BinaryOperator' anstelle eines' BiConsumer' verwendet hat. Aber aus den gegebenen Beispielen ist es klar, dass der Kombinierer sich in seinem ersten Argument ansammeln muss. – Misha

Antwort

6

Scheint, dass dies nicht explizit in der Dokumentation angegeben ist. Allerdings gibt es ein ordering Konzept in Streams 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 die Operation unordered() verwendet. Wenn der Stream bestellt wird, dann sollte die Prozedur auch stabil sein, also denke ich, es ist angenommen, dass für die bestellten Streams die combiner die Argumente in der strikten Reihenfolge erhält. Es ist jedoch nicht für einen ungeordneten Stream garantiert.

9

Natürlich ist es wichtig, wie wenn Sie m2.addAll(m1) anstelle von m1.addAll(m2) verwenden, ändert es nicht nur die Reihenfolge der Elemente, sondern bricht den Vorgang vollständig. Da ein BiConsumer kein Ergebnis zurückgibt, haben Sie keine Kontrolle darüber, welches Objekt der Aufrufer als Ergebnis verwendet, und da der Aufrufer das erste verwendet, führt das Ändern des zweiten statt dessen zu Datenverlust.

Es gibt einen Hinweis, wenn Sie an der Akkumulator aussehen Funktion, die den Typ hat BiConsumer<R,? super T>, mit anderen Worten, das Element des Typs nicht irgend etwas anderes tun kann, als zu speichern T, als zweites Argument vorgesehen ist, in den Behälter des Typs R, als erstes Argument zur Verfügung gestellt.

Wenn man sich die documentation of Collector suchen, die eine BinaryOperator als Kombinierer Funktion verwendet, ermöglicht somit die Kombinierer zu entscheiden, welches Argument (oder sogar ein ganz anderes Ergebnis Instanz) zurückkehren, finden Sie:

Die Assoziativitätsbeschränkung besagt, dass das Teilen der Berechnung ein äquivalentes Ergebnis ergeben muss.Das heißt, für alle Eingabeelemente t1 und t2, die Ergebnisse r1 und r2 bei der Berechnung unten muss gleichwertig sein:

A a1 = supplier.get(); 
accumulator.accept(a1, t1); 
accumulator.accept(a1, t2); 
R r1 = finisher.apply(a1); // result without splitting 

A a2 = supplier.get(); 
accumulator.accept(a2, t1); 
A a3 = supplier.get(); 
accumulator.accept(a3, t2); 
R r2 = finisher.apply(combiner.apply(a2, a3)); // result with splitting 

Wenn wir also davon ausgehen, dass der Akkumulator in Begegnung angelegt wird, die Kombinator muss das erste und das zweite Argument in der Reihenfolge von links nach rechts kombinieren, um ein äquivalentes Ergebnis zu erhalten.


nun die drei argument Version von Stream.collect hat eine etwas andere Signatur, einen BiConsumer als Kombinierer mitexactly for supporting method references like ArrayList::addAll. Unter der Annahme der Konsistenz bei allen diesen Operationen und unter Berücksichtigung des Zwecks dieser Signaturänderung können wir sicher annehmen, dass es das erste Argument sein muss, das der Container zu modifizieren ist.

Aber es scheint, dass dies eine späte Änderung ist und die Dokumentation nicht entsprechend angepasst wurde. Wenn Sie sich den Abschnitt Mutable reduction der Paketdokumentation ansehen, werden Sie feststellen, dass er so angepasst wurde, dass er 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 finisher.apply(combiner.apply(a2, a3)) nicht funktioniert, wenn combiner ein BiConsumer ist ...


Die Dokumentation Problem wurde als JDK-8164691 und adressiert in Java berichtet 9. The new documentation sagt:

Kombinierer - ein assoziatives, nicht-int Englisch: www.weisang.info/index.php?id=143&t...h=cfd8d3eaa9 Erbringen, zustandslose Funktion, die zwei Teilergebnisbehälter akzeptiert und zusammenführt, die mit der Akkumulatorfunktion kompatibel sein müssen Die Combiner-Funktion muss die Elemente aus dem zweiten Ergebniscontainer in den ersten Ergebniscontainer falten.