Scopo del terzo argomento per ridurre la funzione nella programmazione funzionale di Java 8

In quali circostanze è il terzo argomento a ‘ridurre’ chiamato in Java 8 flussi?

Il codice seguente tenta di attraversare un elenco di stringhe e sumre i valori del punto di codice del primo carattere di ciascuno. Il valore restituito dal lambda finale non sembra mai essere usato e, se si inserisce un println, non sembra mai essere invocato. La documentazione lo descrive come un “combinatore” ma non riesco a trovare più dettagli …

int result = data.stream().reduce(0, (total,s) -> total + s.codePointAt(0), (a,b) -> 1000000); 

Stai parlando di questa funzione ?

 reduce  U reduce(U identity, BiFunction accumulator, BinaryOperator combiner) 

Esegue una riduzione sugli elementi di questo stream, utilizzando le funzioni di id quadro, accumulo e combinazione fornite. Questo è equivalente a:

  U result = identity; for (T element : this stream) result = accumulator.apply(result, element) return result; 

ma non è vincolato ad eseguire in sequenza. Il valore dell’id quadro deve essere un’identity framework per la funzione combinatore. Ciò significa che per tutti voi, il combinatore (id quadro, u) è uguale a voi. Inoltre, la funzione combinatore deve essere compatibile con la funzione di accumulatore; per tutti e per te, il seguente deve contenere:

  combiner.apply(u, accumulator.apply(identity, t)) == accumulator.apply(u, t) 

Questa è un’operazione terminale.

Nota dell’API: molte riduzioni che utilizzano questo modulo possono essere rappresentate più semplicemente da una combinazione esplicita di mappe e operazioni di riduzione. La funzione accumulatore agisce come un mappatore e un accumulatore fusi, che a volte possono essere più efficienti della mapping e della riduzione separati, come quando conoscere il valore precedentemente ridotto consente di evitare alcuni calcoli. Tipo Parametri: U – Il tipo del risultato Parametri: id quadro – il valore dell’id quadro per l’accumulatore della funzione combinatore – una funzione associativa, non interferente, stateless per incorporare un elemento aggiuntivo in un combinatore di risultati – un associativo, non interferente, apolide funzione per combinare due valori, che devono essere compatibili con la funzione accumulatore Resi: il risultato della riduzione Vedi anche: ridurre (BinaryOperator), ridurre (Object, BinaryOperator)

Presumo che il suo scopo sia quello di consentire il calcolo parallelo, e quindi la mia ipotesi è che venga usata solo se la riduzione viene eseguita in parallelo. Se viene eseguita in sequenza, non è necessario utilizzare il combiner . Non lo so per certo – sto solo ipotizzando in base al commento del doc “[…] non è costretto ad eseguire in sequenza” e le molte altre menzioni di “esecuzione parallela” nei commenti.

Penso che il paragrafo sulle operazioni di riduzione dal sumrio del pacchetto java.util.stream possa rispondere alla domanda. Lasciatemi citare la parte più importante qui:


Nella sua forma più generale, un’operazione di riduzione sugli elementi di tipo produce un risultato di tipo richiede tre parametri:

  U reduce(U identity, BiFunction accumulator, BinaryOperator combiner); 

Qui, l’elemento id quadro è sia un valore iniziale di seme per la riduzione sia un risultato predefinito se non ci sono elementi di input. La funzione accumulatore prende un risultato parziale e l’elemento successivo e produce un nuovo risultato parziale. La funzione combinatore combina due risultati parziali per produrre un nuovo risultato parziale. (Il combinatore è necessario in riduzioni parallele, dove l’input è partizionato, un accumulo parziale calcolato per ogni partizione, e quindi i risultati parziali vengono combinati per produrre un risultato finale.) Più formalmente, il valore dell’id quadro deve essere un’identity framework per il combinatore funzione. Ciò significa che per tutti u , combiner.apply(identity, u) è uguale a u . Inoltre, la funzione combinatore deve essere associativa e deve essere compatibile con la funzione accumulatore: per tutti e per tutti, combiner.apply(u, accumulator.apply(identity, t)) deve essere equals() per accumulator.apply(u, t) .

La forma a tre argomenti è una generalizzazione della forma a due argomenti, che incorpora una fase di mapping nella fase di accumulazione. Potremmo ri-cast l’esempio di semplice sum di pesi usando la forma più generale come segue:

  int sumOfWeights = widgets.stream() .reduce(0, (sum, b) -> sum + b.getWeight()) Integer::sum); 

sebbene la forma esplicita di riduzione della mappa sia più leggibile e quindi dovrebbe di solito essere preferita. La forma generalizzata viene fornita per i casi in cui è ansible ottimizzare il lavoro significativo combinando la mapping e la riduzione in un’unica funzione.


In altre parole, per quanto ho capito, la forma a tre argomenti è utile in due casi:

  1. Quando l’esecuzione parallela è importante.
  2. Quando è ansible ottenere una significativa ottimizzazione delle prestazioni combinando fasi di mapping e accumulo. In caso contrario è ansible utilizzare un modulo di riduzione della mappa esplicito più semplice e leggibile.

La forma esplicita è menzionata in precedenza nello stesso documento:

 int sumOfWeights = widgets.parallelStream() .filter(b -> b.getColor() == RED) .mapToInt(b -> b.getWeight()) .sum(); 

Semplice codice di test per confermare l’utilizzo del combinatore:

 String[] strArray = {"abc", "mno", "xyz"}; List strList = Arrays.asList(strArray); System.out.println("stream test"); int streamResult = strList.stream().reduce( 0, (total,s) -> { System.out.println("accumulator: total[" + total + "] s[" + s + "] s.codePointAt(0)[" + s.codePointAt(0) + "]"); return total + s.codePointAt(0); }, (a,b) -> { System.out.println("combiner: a[" + a + "] b[" + b + "]"); return 1000000;} ); System.out.println("streamResult: " + streamResult); System.out.println("parallelStream test"); int parallelStreamResult = strList.parallelStream().reduce( 0, (total,s) -> { System.out.println("accumulator: total[" + total + "] s[" + s + "] s.codePointAt(0)[" + s.codePointAt(0) + "]"); return total + s.codePointAt(0); }, (a,b) -> { System.out.println("combiner: a[" + a + "] b[" + b + "]"); return 1000000;} ); System.out.println("parallelStreamResult: " + parallelStreamResult); System.out.println("parallelStream test2"); int parallelStreamResult2 = strList.parallelStream().reduce( 0, (total,s) -> { System.out.println("accumulator: total[" + total + "] s[" + s + "] s.codePointAt(0)[" + s.codePointAt(0) + "]"); return total + s.codePointAt(0); }, (a,b) -> { System.out.println("combiner: a[" + a + "] b[" + b + "] a+b[" + (a+b) + "]"); return a+b;} ); System.out.println("parallelStreamResult2: " + parallelStreamResult2); 

Produzione:

 stream test accumulator: total[0] s[abc] s.codePointAt(0)[97] accumulator: total[97] s[mno] s.codePointAt(0)[109] accumulator: total[206] s[xyz] s.codePointAt(0)[120] streamResult: 326 parallelStream test accumulator: total[0] s[mno] s.codePointAt(0)[109] accumulator: total[0] s[abc] s.codePointAt(0)[97] accumulator: total[0] s[xyz] s.codePointAt(0)[120] combiner: a[109] b[120] combiner: a[97] b[1000000] parallelStreamResult: 1000000 parallelStream test2 accumulator: total[0] s[mno] s.codePointAt(0)[109] accumulator: total[0] s[xyz] s.codePointAt(0)[120] accumulator: total[0] s[abc] s.codePointAt(0)[97] combiner: a[109] b[120] a+b[229] combiner: a[97] b[229] a+b[326] parallelStreamResult2: 326