Faire la somme d’une liste de nombres en Java
22 mai 2020
0 minutes de lectureDe temps en temps, j’ai besoin de faire des opérations simples en Java et je me demande quelle est la meilleure façon de procéder. C’est ce qui m’est arrivé il y a quelques jours ! Je voulais simplement calculer la somme d’une liste de nombres et j’ai découvert qu’il y avait plusieurs façons — le jeu de mots est voulu — de le faire.
La méthode à l’ancienne
Nous pouvons créer une boucle simple pour cela. J’utilise Java 11, alors pardonnez-moi si vous utilisez, par exemple, Java 8 et que List.of et var ne fonctionnent pas dans votre cas. Vous devriez néanmoins comprendre l’idée.
Bien sûr, depuis Java 5, les boucles for ont été améliorées. Je peux donc réécrire le même code comme ceci.
La différence est subtile. Cependant, le code est déjà plus explicite : il signifie quelque chose comme « pour chaque number provenant de listOfNumbers, je veux faire ce qui suit… ».
L’approche avec les flux Java
Ceux qui me connaissent savent que, pendant mes études universitaires, on m’a conditionné à programmer en Haskell. J’ai donc un faible pour la programmation fonctionnelle pure. Java n’est pas vraiment conçu pour ça ?, mais l’API Stream permet d’accéder dans une certaine mesure à l’expressivité de la programmation fonctionnelle.
Avec l’API Stream de Java, nous pouvons appliquer le modèle de programmation MapReduce. Pour le problème que j’essaie de résoudre ici, je n’ai pas besoin d’effectuer de mappage, puisque les nombres resteront tels quels. En revanche, je dois réduire la liste à un seul nombre : la somme.
Collecter
Dans probablement 99 % des cas, nous utilisons la fonction collect avec le collecteur standard toList() pour transformer à nouveau notre flux en liste. Par exemple :
Mais il y a bien plus à faire que de collecter un flux pour le transformer à nouveau en liste. En parcourant la bibliothèque Collectors, vous trouverez des fonctions comme summingInt(), summingDouble() et summingLong(). Vous pouvez les utiliser pour collecter (ou réduire) la liste et en obtenir la somme.
La fonction summmingInt nécessite une fonction qui convertit l’entrée dont vous disposez en int. Dans ce cas, je peux simplement utiliser la « fonction identité ». La fonction i -> i suffit.
Cette fonction identité peut sembler inutile ; vous pouvez donc utiliser Integer.intValue() à la place.
Quand je fais cela, mon IDE — IntelliJ IDEA dans mon cas — me conseille de refactoriser le code et d’utiliser la fonction mapToInt(), comme ci-dessous :
Techniquement, nous associons ici chaque élément à un int, ce qu’il est déjà ¯\(ã)/¯, puis nous le réduisons avec la fonction sum().
Cela devient plus clair si vous regardez les types inférés. Il est tout simplement impossible d’avoir une liste de types primitifs. La liste contient donc des Integer (des objets). Cela signifie que chaque élément de la liste doit être reconverti en int primitif pour permettre l’appel de sum(). L’exemple précédent, avec la fonction identité dans le collecteur, fonctionne grâce au déballage automatique des types en Java.
Si vous préférez utiliser des listes de types primitifs en Java, je vous conseille de découvrir la bibliothèque Eclipse Collections.
Réduire
En Java, la réduction s’effectue à l’aide de plusieurs fonctions de l’API Stream. En plus de collect(), il existe également la fonction au nom explicite reduce().
Dans ce cas, la fonction reduce prend un point de départ et une expression lambda BiFunction. La BiFunction est appliquée au point de départ et au premier nombre ; le résultat de la fonction est ensuite appliqué au deuxième nombre, et ainsi de suite.
Le code ci-dessus effectue quelque chose comme ceci : 0 + 1, puis + 2, puis + 3, puis + 4, etc.
Vous pouvez maintenant omettre le point de départ 0. Toutefois, dans ce cas, la fonction reduce renvoie un Optional, car la liste qu’elle tente de réduire peut être vide.
Conclusion
Comme vous pouvez le constater, il existe plusieurs façons de résoudre ce problème. Il ne fait aucun doute que certains trouveront des solutions encore plus exotiques. Ma préférée est l’approche avec reduce(). À mes yeux, c’est la solution la plus expressive et la plus pure en Java. Je veux simplement réduire une liste à un seul nombre, sans avoir à me soucier des conversions entre les types enveloppés et les types primitifs. De plus, je peux réutiliser cette approche pour réduire une liste d’autres types en écrivant une fonction lambda de réduction adaptée à mes besoins.
