Come sommare un elenco di numeri interi con flussi Java?


365

Voglio riassumere un elenco di numeri interi. Funziona come segue, ma la sintassi non sembra corretta. Il codice potrebbe essere ottimizzato?

Map<String, Integer> integers;
integers.values().stream().mapToInt(i -> i).sum();

5
"ma la sintassi non sembra corretta" Cosa te lo fa pensare? Questo è il solito linguaggio. Forse vuoi usare mapToLongper evitare overflow, a seconda dei valori che la tua mappa può avere.
Alexis C.,

3
@JBNizet Trovo i -> imolto chiaro, personalmente. Bene, sì, devi sapere che il valore verrà automaticamente decompresso, ma è vero dal momento che Java 5 ...
Alexis C.,

4
@AlexisC. è comprensibile perché è passato a mapToInt () e perché sono uno sviluppatore esperto. Ma io -> io, senza contesto, sembra un noop. Intero :: intValue è più dettagliato, ma rende esplicita l'operazione di unboxing.
JB Nizet,

1
@JBNizet Le persone che chiamano il metodo foo(int i)non scrivono foo(myInteger.intValue());ogni volta che lo chiamano (o almeno non mi aspetto !!). Sono d'accordo con te che Integer::intValueè più esplicito, ma penso che lo stesso valga qui. Le persone dovrebbero semplicemente impararlo una volta e poi il gioco è fatto :-). Non è come se fosse un po 'di offuscamento magico.
Alexis C.,

4
@JB Nizet: beh, i -> isembra un no-op e, concettualmente, è un no-op. Certo, sotto il cofano Integer.intValue()viene chiamato, ma ancora più in profondità sotto il cofano, che i metodi vengono incorporati per diventare esattamente la no-op che sembra nel codice sorgente. Integer::intValueha il punto bonus di non creare un metodo sintetico nel codice byte ma non è ciò che dovrebbe guidare la tua decisione su come organizzare il tuo codice sorgente.
Holger

Risposte:


499

Funzionerà, ma i -> ista facendo unboxing automatico ed è per questo che "sembra" strano. Uno dei seguenti funzionerà e spiegherà meglio cosa sta facendo il compilatore sotto la cappa con la sintassi originale:

integers.values().stream().mapToInt(i -> i.intValue()).sum();
integers.values().stream().mapToInt(Integer::intValue).sum();

2
E se avessimo un BigInteger :)?
GOXR3PLUS

13
Un'opzione semplice èBigDecimal sum = numbers.stream().reduce(BigDecimal.ZERO, BigDecimal::add);
Matteo,

158

Suggerisco altre 2 opzioni:

integers.values().stream().mapToInt(Integer::intValue).sum();
integers.values().stream().collect(Collectors.summingInt(Integer::intValue));

Il secondo usa il Collectors.summingInt()raccoglitore, c'è anche un summingLong()raccoglitore con cui useresti mapToLong.


E una terza opzione: Java 8 introduce un LongAdderaccumulatore molto efficace progettato per accelerare il riepilogo in flussi paralleli e ambienti multi-thread. Ecco un esempio di utilizzo:

LongAdder a = new LongAdder();
map.values().parallelStream().forEach(a::add);
sum = a.intValue();

86

Dai documenti

Operazioni di riduzione Un'operazione di riduzione (chiamata anche piega) prende una sequenza di elementi di input e li combina in un unico risultato di riepilogo mediante l'applicazione ripetuta di un'operazione di combinazione, come trovare la somma o il massimo di un insieme di numeri o accumulare elementi in una lista. Le classi di flussi hanno più forme di operazioni generali di riduzione, chiamate riduzioni () e collect (), nonché più forme di riduzione specializzate come sum (), max () o count ().

Naturalmente, tali operazioni possono essere prontamente implementate come semplici cicli sequenziali, come in:

int sum = 0;
for (int x : numbers) {
   sum += x;
}

Tuttavia, ci sono buoni motivi per preferire un'operazione di riduzione rispetto ad un accumulo mutativo come sopra. Non solo una riduzione è "più astratta" - opera sul flusso nel suo insieme piuttosto che su singoli elementi - ma un'operazione di riduzione correttamente costruita è intrinsecamente parallelizzabile, purché le funzioni utilizzate per elaborare gli elementi siano associative e apolide. Ad esempio, dato un flusso di numeri per cui vogliamo trovare la somma, possiamo scrivere:

int sum = numbers.stream().reduce(0, (x,y) -> x+y);

o:

int sum = numbers.stream().reduce(0, Integer::sum);

Queste operazioni di riduzione possono essere eseguite in sicurezza in parallelo senza quasi nessuna modifica:

int sum = numbers.parallelStream().reduce(0, Integer::sum);

Quindi, per una mappa dovresti usare:

integers.values().stream().mapToInt(i -> i).reduce(0, (x,y) -> x+y);

O:

integers.values().stream().reduce(0, Integer::sum);

2
Ciò che l'OP ha è molto meglio e anche più chiaro. Questo codice implicherebbe un intero blocco di operazioni di unboxing e boxe.
JB Nizet,

1
@JBNizet A meno che l'analisi di escape elimini il pugilato. Dovresti provarlo per vedere se può.
Peter Lawrey,

6
(x, y) -> x + y deve deselezionare x e y, sommarli e quindi riordinare il risultato. E ricominciare per aggiungere il risultato con l'elemento successivo dello stream e ancora e ancora.
JB Nizet,

3
Intero :: somma soffre dello stesso problema. E se usi mapToInt () per avere un IntStream, chiamare sum () su di esso è più semplice di chiamare riduci ().
JB Nizet,

3
Vedi docs.oracle.com/javase/8/docs/api/java/lang/… . I due argomenti di Integer.sum () sono di tipo int. Quindi i due numeri interi dallo stream devono essere unboxed per essere passati come argomenti al metodo. Il metodo restituisce un int, ma riduci () accetta un oggetto BinaryOperator <Integer> come argomento, che quindi restituisce un numero intero. Quindi il risultato della somma deve essere inscatolato in Intero.
JB Nizet,

28

Puoi usare il metodo di riduzione:

long sum = result.stream().map(e -> e.getCreditAmount()).reduce(0L, (x, y) -> x + y);

o

long sum = result.stream().map(e -> e.getCreditAmount()).reduce(0L, Integer::sum);

9
Esiste già un tale accumulatore int, lo èInteger::sum
Alex Salauyou,

1
Stai tornando a lungo, quindi sarebbe meglio Long::sumdi Integer::sum.
Andrei Damian-Fekete,

16

È possibile utilizzare reduce()per sommare un elenco di numeri interi.

int sum = integers.values().stream().reduce(0, Integer::sum);

11

È possibile utilizzare il metodo di raccolta per aggiungere un elenco di numeri interi.

List<Integer> list = Arrays.asList(2, 4, 5, 6);
int sum = list.stream().collect(Collectors.summingInt(Integer::intValue));

6

Questo sarebbe il modo più breve per riassumere il inttipo di array (per longarray LongStream, per doublearray DoubleStreame così via). StreamTuttavia, non tutti i tipi interi primitivi o in virgola mobile hanno l' implementazione.

IntStream.of(integers).sum();

Sfortunatamente non abbiamo alcun int-array. Quindi IntStream.of()non funzionerà per questo problema, a meno che non stiamo facendo qualcosa di spaventoso come questo:IntStream.of( integers.values().stream().mapToInt( Integer::intValue ).toArray() ).sum();
Kaplan,

Non è necessario, questo sarebbe sufficiente integers.values().stream().mapToInt( Integer::intValue ).sum().
Sachith Dickwella,

3

Possa questo aiutare coloro che hanno oggetti nell'elenco.

Se si dispone di un elenco di oggetti e si desidera sommare campi specifici di questo oggetto, utilizzare quanto segue.

List<ResultSom> somList = MyUtil.getResultSom();
BigDecimal result= somList.stream().map(ResultSom::getNetto).reduce(
                                             BigDecimal.ZERO, BigDecimal::add);

Grazie, mi ha aiutato in uno dei miei scenari
A_01

1

Ho dichiarato un elenco di numeri interi.

ArrayList<Integer> numberList = new ArrayList<Integer>(Arrays.asList(1, 2, 3, 4, 5));

Puoi provare a utilizzare questi diversi modi di seguito.

utilizzando mapToInt

int sum = numberList.stream().mapToInt(Integer::intValue).sum();

utilizzando summarizingInt

int sum = numberList.stream().collect(Collectors.summarizingInt(Integer::intValue)).getSum();

utilizzando reduce

int sum = numberList.stream().reduce(Integer::sum).get().intValue();

-1
class Pojo{
    int num;

    public Pojo(int num) {
        super();
        this.num = num;
    }

    public int getNum() {
        return num;
    }

    public void setNum(int num) {
        this.num = num;
    }
}

List<Pojo> list = new ArrayList<Pojo>();
            list.add(new Pojo(1));
            list.add(new Pojo(5));
            list.add(new Pojo(3));
            list.add(new Pojo(4));
            list.add(new Pojo(5));

            int totalSum = list.stream().mapToInt(pojo -> pojo.getNum()).sum();
            System.out.println(totalSum);

-1

La maggior parte degli aspetti sono coperti. Ma potrebbe esserci la necessità di trovare l'aggregazione di altri tipi di dati oltre a Integer, Long (per i quali è già presente il supporto di stream specializzato). Per esempio stram con BigInteger Per un tale tipo possiamo usare operazioni ridotte come

list.stream (). reduce ((bigInteger1, bigInteger2) -> bigInteger1.add (bigInteger2))

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.