Skip to main content

Faire la somme d’une liste de nombres en Java

Écrit par
Java engineering feature

22 mai 2020

0 minutes de lecture

De 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.

var listOfNumbers = List.of(1,2,3,4,5,6,7,8,9,10);

var sum = 0;
for (int i = 0; i < listOfNumbers.size() ; i++) {
    sum += listOfNumbers.get(i);
}

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.

var listOfNumbers = List.of(1,2,3,4,5,6,7,8,9,10);

var sum = 0;
for (int number : listOfNumbers) {
    sum += number;
}

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 :

       var time2ToList = listOfNumbers.stream()
                .map(i -> i * 2)
                .collect(Collectors.toList());

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.

        var listOfNumbers = List.of(1,2,3,4,5,6,7,8,9,10);
        var sum = listOfNumbers.stream()
                    .collect(Collectors.summingInt(i -> i));

Cette fonction identité peut sembler inutile ; vous pouvez donc utiliser Integer.intValue() à la place.

       var listOfNumbers = List.of(1,2,3,4,5,6,7,8,9,10);
       var sum = listOfNumbers.stream()
                .collect(Collectors.summingInt(Integer::intValue));

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 :

       var listOfNumbers = List.of(1,2,3,4,5,6,7,8,9,10);
       var sum = listOfNumbers.stream()
                .mapToInt(Integer::intValue).sum();

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().

       var listOfNumbers = List.of(1,2,3,4,5,6,7,8,9,10);
       var sum = listOfNumbers.stream()
                .reduce(0 , (num1, num2) -> num1 + num2);

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.

Publié dans: