2015-05-29 18 views
8

Il Java documentazioni API afferma che il parametro combiner del metodo collect deve essere:Dove è definito l'ordine di combinazione del combinatore di raccolta (fornitore, accumulatore, combinatore)?

associativo, non interferente, funzione stateless per combinare due valori, che devono essere compatibili con la funzione di accumulatore

A combiner è un BiConsumer<R,R> che riceve due parametri di tipo R e restituisce void. Ma la documentazione non dice se dovremmo combinare gli elementi nel primo o nel secondo parametro?

Ad esempio i seguenti esempi possono dare risultati diversi, a seconda dell'ordine di combinazione: m1.addAll(m2) o 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)); 

So che in questo caso si potrebbe usare semplicemente un manico metodo, ad esempio ArrayList::addAll. Tuttavia, ci sono alcuni casi in cui è richiesto un Lambda e dobbiamo combinare gli articoli nell'ordine corretto, altrimenti potremmo ottenere un risultato incoerente durante l'elaborazione in parallelo.

Questo è affermato in qualsiasi parte della documentazione dell'API Java 8? O davvero non importa?

+2

Hai ragione, il javadoc non è scritto molto chiaramente - confusamente, dice che il combinatore deve essere associativo !. Probabilmente è un residuo di una versione precedente, che utilizzava un 'BinaryOperator' piuttosto che un' BiConsumer'. Ma dagli esempi forniti è chiaro che il combinatore deve accumularsi nel suo primo argomento. – Misha

risposta

6

Sembra che questo non sia esplicitamente indicato nella documentazione. Tuttavia c'è un concetto ordering nell'API dei flussi. Stream può essere ordinato o meno. Può essere non ordinato dall'inizio se lo splitteratore di origine non è ordinato (ad esempio, se la sorgente del flusso è HashSet). O lo stream può diventare non ordinato se l'utente utilizza esplicitamente l'operazione unordered(). Se lo stream è ordinato, anche la procedura di raccolta dovrebbe essere stabile, quindi, suppongo, è assunto che per gli stream ordinati lo combiner riceve gli argomenti nell'ordine stretto. Tuttavia non è garantito per un flusso non ordinato.

9

Naturalmente, è importante, come quando si utilizza m2.addAll(m1) anziché m1.addAll(m2), non solo si modifica l'ordine degli elementi, ma si interrompe completamente l'operazione. Poiché un BiConsumer non restituisce un risultato, non si ha il controllo su quale oggetto verrà utilizzato dal chiamante come risultato e poiché il chiamante utilizzerà il primo, la modifica del secondo causerà la perdita di dati.

v'è un accenno, se si guarda alla funzione diaccumulatore che ha il tipo BiConsumer<R,? super T>, in altre parole non possono fare altro che memorizzare l'elemento di tipo T, fornito come secondo argomento, nel contenitore di tipo R, fornito come primo argomento.

Se si guarda al documentation of Collector, che utilizza un BinaryOperator come combinatore funzione, quindi permette al combinatore per decidere quale argomento di ritorno (o anche un'istanza risultato completamente diverso), ci si trova:

Il vincolo di associatività dice che la divisione del calcolo deve produrre un risultato equivalente.Cioè, per eventuali elementi di input t1 e t2, i risultati r1 e r2 nel calcolo sottostante deve essere equivalente:

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 

Quindi, se si assume che il accumulatore viene applicata per incontro, il il combinatore deve combinare il primo e il secondo argomento nell'ordine da sinistra a destra per produrre un risultato equivalente.


Ora, la versione a tre-arg di Stream.collect ha una firma leggermente diverso, utilizzando un BiConsumer come combinatoreexactly for supporting method references like ArrayList::addAll. Supponendo la coerenza in tutte queste operazioni e considerando lo scopo di questa modifica della firma, possiamo tranquillamente supporre che debba essere il primo argomento che è il contenitore da modificare.

Ma sembra che questo sia un cambiamento tardivo e la documentazione non è stata adattata di conseguenza. Se si guarda la sezione Mutable reduction della documentazione del pacchetto, si scoprirà che è stata adattata per mostrare la firma e gli esempi di utilizzo effettivi di Stream.collect, ma ripropone esattamente la stessa definizione relativa al vincolo di associatività come mostrato sopra, nonostante il fatto che finisher.apply(combiner.apply(a2, a3)) non funziona se combiner è un BiConsumer ...


La questione documentazione è stato segnalato come JDK-8164691 e indirizzata in Java 9. The new documentation dice:

combinatore - un associativa, non int erfering, funzione stateless che accetta due contenitori di risultati parziali e li unisce, che devono essere compatibili con la funzione di accumulatore. La funzione combinatore deve piegare gli elementi dal secondo contenitore dei risultati nel primo contenitore dei risultati.

Problemi correlati