Eine Liste von Zahlen in Java summieren
22. Mai 2020
0 Min. LesezeitHin und wieder muss ich in Java einfache Dinge erledigen und frage mich, wie das am besten geht. Vor ein paar Tagen war es wieder so weit! Ich wollte einfach die Summe einer Liste mit Zahlen berechnen und stellte fest, dass es dafür – Wortspiel beabsichtigt – mehrere Möglichkeiten gibt.
Der altmodische Ansatz
Dafür können wir eine einfache Schleife erstellen. Ich verwende Java 11. Verzeihen Sie mir also, falls Sie beispielsweise Java 8 verwenden und List.of und var bei Ihnen nicht funktionieren. Ich bin aber sicher, Sie verstehen trotzdem, worauf ich hinauswill.
Seit Java 5 gibt es außerdem die erweiterte for-Schleife. Damit kann ich denselben Code so umschreiben:
Der Unterschied ist subtil. Der Code ist aber bereits ausdrucksstärker, denn er sagt sinngemäß: „Für jede number, die aus listOfNumbers kommt, möchte ich Folgendes tun …“
Der Java-Stream-Ansatz
Wer mich kennt, weiß, dass ich während meines Studiums auf die Programmierung mit Haskell eingeschworen wurde. Deshalb liebe ich die rein funktionale Programmierung. Nicht, dass Java das wirklich beherrschen würde 😅, aber mit der Stream-API steht die Ausdrucksstärke der funktionalen Programmierung teilweise zur Verfügung.
Mit der Stream-API in Java können wir das MapReduce-Programmiermodell ausführen. Für das Problem, das ich hier lösen möchte, brauche ich kein Mapping, da die Zahlen unverändert bleiben. Ich muss die Liste jedoch auf eine einzelne Zahl reduzieren: die Summe.
Collect
In vermutlich 99 % der Fälle verwenden wir die Funktion collect mit dem standardmäßigen toList()-Collector, um unseren Stream wieder in eine Liste umzuwandeln. Zum Beispiel so:
Es gibt jedoch mehr als nur das Sammeln eines Streams in einer Liste. Wenn Sie die Collectors-Bibliothek durchstöbern, finden Sie Funktionen wie summingInt(), summingDouble() und summingLong(). Mit diesen Funktionen können Sie die Liste zur Summe zusammenfassen (oder reduzieren).
Die Funktion summmingInt benötigt eine Funktion, die Ihre Eingabe in einen int umwandelt. In diesem Fall kann ich einfach die „Identitätsfunktion“ verwenden. Die Funktion i -> i reicht aus.
Diese Identitätsfunktion wirkt vielleicht unnötig. Stattdessen können Sie auch Integer.intValue() verwenden.
Wenn ich das mache, empfiehlt mir meine IDE – in meinem Fall IntelliJ IDEA –, den Code umzugestalten und die Funktion mapToInt() wie unten gezeigt zu verwenden:
Technisch gesehen ordnen wir hier jedes Element einem int zu – obwohl es das bereits ist ¯\(ã)/¯ – und reduzieren es dann mit der Funktion sum().
Deutlicher wird es, wenn Sie sich die abgeleiteten Typen ansehen. Eine Liste aus primitiven Werten gibt es nicht. Die Liste enthält also Integer-Objekte. Das bedeutet, dass jedes Element in der Liste wieder in den primitiven Typ int umgewandelt werden muss, damit sum() funktioniert. Das vorherige Beispiel mit der Identitätsfunktion im Collector funktioniert dank des Unboxings in Java.
Wenn Sie in Java lieber Listen mit primitiven Werten verwenden, empfehle ich Ihnen einen Blick auf die Bibliothek Eclipse Collections.
Reduce
In Java wird eine Reduktion mit einigen Funktionen der Stream-API erreicht. Neben collect() gibt es auch die naheliegenderweise reduce() genannte Funktion.
Die Funktion reduce erhält in diesem Fall einen Startwert und einen Lambda-Ausdruck vom Typ BiFunction. Die BiFunction wird auf den Startwert und die erste Zahl angewendet. Das Ergebnis der Funktion wird dann auf die zweite Zahl angewendet und so weiter.
Der obige Code führt sinngemäß Folgendes aus: 0 + 1, dann + 2, dann + 3, dann + 4 und so weiter …
Sie können den Startwert 0 auch weglassen. In diesem Fall gibt die Funktion reduce jedoch ein Optional zurück, da die zu reduzierende Liste leer sein könnte.
Fazit
Wie Sie sehen, gibt es mehrere Möglichkeiten, dieses Problem zu lösen. Zweifellos werden sich noch weitere, exotischere Lösungswege finden. Mein persönlicher Favorit ist die Variante mit reduce(). Für mich ist sie die ausdrucksstärkste und reinste Lösung in Java. Ich möchte einfach eine Liste auf eine einzelne Zahl reduzieren und muss mich nicht um die Umwandlung von Boxed-Typen in primitive Typen kümmern. Außerdem kann ich diesen Ansatz wiederverwenden, wenn ich eine Liste anderer Typen reduzieren muss: Dafür schreibe ich einfach einen Reduktions-Lambda-Ausdruck, der meinen Anforderungen entspricht.
