Wie summiere ich eine Liste von ganzen Zahlen mit Java-Streams?

364

Ich möchte eine Liste von ganzen Zahlen zusammenfassen. Es funktioniert wie folgt, aber die Syntax fühlt sich nicht richtig an. Könnte der Code optimiert werden?

Map<String, Integer> integers;
integers.values().stream().mapToInt(i -> i).sum();
Mitgliedssound
quelle
5
"aber die Syntax fühlt sich nicht richtig an" Was lässt Sie das denken? Dies ist die übliche Redewendung. Vielleicht möchten Sie verwenden mapToLong, um Überläufe zu vermeiden, abhängig von den Werten, die Ihre Karte haben kann.
Alexis C.
3
@JBNizet finde ich i -> ipersönlich sehr klar. Nun ja, Sie müssen wissen, dass der Wert automatisch entpackt wird, aber es ist wahr seit Java 5 ...
Alexis C.
4
@AlexisC. Es ist verständlich, weil es an mapToInt () übergeben wird und weil ich ein erfahrener Entwickler bin. Aber ich -> ich, ohne Kontext, sieht aus wie ein Noop. Integer :: intValue ist ausführlicher, macht den Unboxing-Vorgang jedoch explizit.
JB Nizet
1
@JBNizet Leute, die die Methode aufrufen, foo(int i)schreiben nicht foo(myInteger.intValue());jedes Mal , wenn sie sie aufrufen (oder zumindest erwarte ich das nicht !!). Ich stimme Ihnen zu, das Integer::intValueist expliziter, aber ich denke, das gilt auch hier. Die Leute sollten es nur einmal lernen und dann bist du fertig :-). Es ist nicht so, als wäre es eine magische Verschleierung.
Alexis C.
4
@JB Nizet: gut, i -> isieht aus wie ein no-op und konzeptuell, es ist ein No-op. Sicher, unter der Haube Integer.intValue()wird aufgerufen, aber noch tiefer unter der Haube werden diese Methoden eingefügt, um genau das No-Op zu werden, wie es im Quellcode aussieht. Integer::intValuehat den Bonuspunkt, keine synthetische Methode im Bytecode zu erstellen, aber es ist nicht das, was Ihre Entscheidung über die Organisation Ihres Quellcodes beeinflussen sollte.
Holger

Antworten:

498

Dies wird funktionieren, aber das i -> imacht ein automatisches Unboxing, weshalb es sich seltsam "anfühlt". Beides funktioniert und erklärt besser, was der Compiler unter der Haube mit Ihrer ursprünglichen Syntax tut:

integers.values().stream().mapToInt(i -> i.intValue()).sum();
integers.values().stream().mapToInt(Integer::intValue).sum();
Necreaux
quelle
2
Was ist, wenn wir eine BigInteger haben :)?
GOXR3PLUS
13
Eine einfache Option istBigDecimal sum = numbers.stream().reduce(BigDecimal.ZERO, BigDecimal::add);
Matthew
158

Ich schlage 2 weitere Optionen vor:

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

Der zweite verwendet Collectors.summingInt()Collector, es gibt auch einen summingLong()Collector, mit dem Sie verwenden würden mapToLong.


Und eine dritte Option: Java 8 führt einen sehr effektiven LongAdderAkkumulator ein, der die Zusammenfassung in parallelen Streams und Multithread-Umgebungen beschleunigt. Hier ist ein Beispiel:

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

Aus den Dokumenten

Reduktionsoperationen Eine Reduktionsoperation (auch als Fold bezeichnet) nimmt eine Folge von Eingabeelementen und kombiniert sie zu einem einzigen zusammenfassenden Ergebnis, indem wiederholt eine Kombinationsoperation angewendet wird, z. B. das Ermitteln der Summe oder des Maximums eines Satzes von Zahlen oder das Akkumulieren von Elementen eine Liste. Die Streams-Klassen haben mehrere Formen allgemeiner Reduktionsoperationen, die als redu () und collect () bezeichnet werden, sowie mehrere spezialisierte Reduktionsformen wie sum (), max () oder count ().

Natürlich können solche Operationen leicht als einfache sequentielle Schleifen implementiert werden, wie in:

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

Es gibt jedoch gute Gründe, eine Reduktionsoperation einer mutativen Akkumulation wie der obigen vorzuziehen. Eine Reduktion ist nicht nur "abstrakter" - sie wirkt sich eher auf den gesamten Stream als auf einzelne Elemente aus -, sondern eine ordnungsgemäß aufgebaute Reduktionsoperation ist von Natur aus parallelisierbar, solange die zur Verarbeitung der Elemente verwendeten Funktionen assoziativ sind und staatenlos. Wenn wir beispielsweise einen Zahlenstrom angeben, für den wir die Summe ermitteln möchten, können wir schreiben:

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

oder:

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

Diese Reduktionsvorgänge können ohne nahezu Änderung sicher parallel ausgeführt werden:

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

Für eine Karte würden Sie also Folgendes verwenden:

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

Oder:

integers.values().stream().reduce(0, Integer::sum);
J Atkin
quelle
2
Was das OP hat, ist viel besser und auch klarer. Dieser Code würde eine ganze Reihe von Unboxing- und Boxing-Vorgängen beinhalten.
JB Nizet
1
@JBNizet Es sei denn, die Escape-Analyse eliminiert das Boxen. Sie müssten es versuchen, um zu sehen, ob es kann.
Peter Lawrey
6
(x, y) -> x + y muss x und y entpacken, summieren und dann das Ergebnis verpacken. Beginnen Sie erneut, um das Ergebnis mit dem nächsten Element des Streams hinzuzufügen, und zwar immer wieder.
JB Nizet
3
Integer :: sum leidet unter dem gleichen Problem. Und wenn Sie mapToInt () verwenden, um einen IntStream zu haben, ist das Aufrufen von sum () einfacher als das Aufrufen von redu ().
JB Nizet
3
Siehe docs.oracle.com/javase/8/docs/api/java/lang/… . Die beiden Argumente von Integer.sum () sind vom Typ int. Daher müssen die beiden Ganzzahlen aus dem Stream entpackt werden, um als Argumente an die Methode übergeben zu werden. Die Methode gibt ein int zurück, redu () verwendet jedoch einen BinaryOperator <Integer> als Argument, der somit eine Ganzzahl zurückgibt. Das Ergebnis der Summe muss also in Integer gesetzt werden.
JB Nizet
28

Sie können die Reduzierungsmethode verwenden:

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

oder

long sum = result.stream().map(e -> e.getCreditAmount()).reduce(0L, Integer::sum);
Saeed Zarinfam
quelle
9
Es gibt bereits einen solchen Akkumulator für int, es istInteger::sum
Alex Salauyou
1
Du kommst lange zurück, also wäre es besser Long::sumals Integer::sum.
Andrei Damian-Fekete
16

Sie können reduce()eine Liste von Ganzzahlen summieren.

int sum = integers.values().stream().reduce(0, Integer::sum);
Johnson Abraham
quelle
11

Mit der Methode collect können Sie eine Liste von Ganzzahlen hinzufügen.

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

Dies wäre der kürzeste Weg, um den intTyp Array (für longArray LongStream, für doubleArray DoubleStreamusw.) zusammenzufassen. Allerdings haben nicht alle primitiven Ganzzahl- oder Gleitkommatypen die StreamImplementierung.

IntStream.of(integers).sum();
Sachith Dickwella
quelle
Leider haben wir kein Int-Array. Also IntStream.of()wird es für dieses Problem nicht funktionieren, es sei denn, wir machen so etwas Gruseliges:IntStream.of( integers.values().stream().mapToInt( Integer::intValue ).toArray() ).sum();
Kaplan
Keine Notwendigkeit, das wäre genug integers.values().stream().mapToInt( Integer::intValue ).sum().
Sachith Dickwella
3

Möge dies denen helfen, die Objekte auf der Liste haben.

Wenn Sie eine Liste von Objekten haben und bestimmte Felder dieses Objekts summieren möchten, verwenden Sie Folgendes.

List<ResultSom> somList = MyUtil.getResultSom();
BigDecimal result= somList.stream().map(ResultSom::getNetto).reduce(
                                             BigDecimal.ZERO, BigDecimal::add);
itro
quelle
Danke, es hat mir in einem meiner Szenarien geholfen
A_01
1

Ich habe eine Liste von Ganzzahlen deklariert.

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

Sie können diese verschiedenen Methoden unten verwenden.

Verwenden von mapToInt

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

Verwenden von summarizingInt

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

Verwenden von reduce

int sum = numberList.stream().reduce(Integer::sum).get().intValue();
JDGuide
quelle
-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);
Ranjit Soni
quelle
-1

Die meisten Aspekte werden behandelt. Es könnte jedoch erforderlich sein, die Aggregation anderer Datentypen außer Integer, Long (für die bereits spezielle Stream-Unterstützung vorhanden ist) zu finden. Zum Beispiel stram mit BigInteger Für einen solchen Typ können wir Reduktionsoperationen wie verwenden

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

ManojG
quelle