Comment additionner une liste d'entiers avec des java streams?

365

Je veux résumer une liste d'entiers. Cela fonctionne comme suit, mais la syntaxe ne semble pas correcte. Le code pourrait-il être optimisé?

Map<String, Integer> integers;
integers.values().stream().mapToInt(i -> i).sum();
membersound
la source
5
"mais la syntaxe ne semble pas correcte" Qu'est-ce qui vous fait penser cela? C'est l'idiome habituel. Peut-être que vous souhaitez utiliser mapToLongpour éviter les débordements, selon les valeurs que votre carte peut avoir.
Alexis C.
3
@JBNizet je trouve i -> itrès clair, personnellement. Bon, oui il faut savoir que la valeur sera automatiquement déballée, mais c'est vrai depuis Java 5 ...
Alexis C.
4
@AlexisC. c'est compréhensible parce qu'il est passé à mapToInt (), et parce que je suis un développeur expérimenté. Mais i -> i, sans contexte, ressemble à un noop. Integer :: intValue est plus verbeux, mais rend l'opération de déballage explicite.
JB Nizet
1
@JBNizet Les gens qui appellent la méthode foo(int i)n'écrivent pas à foo(myInteger.intValue());chaque fois qu'ils l'appellent (ou du moins je m'attends à ce que non !!). Je suis d'accord avec vous, c'est Integer::intValueplus explicite, mais je pense que la même chose s'applique ici. Les gens devraient juste l'apprendre une fois et vous avez terminé :-). Ce n'est pas comme si c'était de l'obscurcissement magique.
Alexis C.
4
@JB Nizet: bien, i -> iressemble à un no-op et conceptuellement, il est un non-op. Bien sûr, sous le capot Integer.intValue()est appelé, mais encore plus profondément sous le capot, ces méthodes sont intégrées pour devenir exactement le no-op auquel elles ressemblent dans le code source. Integer::intValuea l'avantage de ne pas créer de méthode synthétique dans le code octet, mais ce n'est pas ce qui devrait motiver votre décision sur la façon d'organiser votre code source.
Holger

Réponses:

499

Cela fonctionnera, mais le i -> ifait un déballage automatique, c'est pourquoi cela "semble" étrange. L'un des éléments suivants fonctionnera et expliquera mieux ce que le compilateur fait sous le capot avec votre syntaxe d'origine:

integers.values().stream().mapToInt(i -> i.intValue()).sum();
integers.values().stream().mapToInt(Integer::intValue).sum();
Necreaux
la source
2
Et si nous avons un BigInteger :)?
GOXR3PLUS du
13
Une option simple estBigDecimal sum = numbers.stream().reduce(BigDecimal.ZERO, BigDecimal::add);
Matthew
158

Je suggère 2 options supplémentaires:

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

Le second utilise le Collectors.summingInt()collecteur, il y a aussi un summingLong()collecteur avec lequel vous utiliseriez mapToLong.


Et une troisième option: Java 8 introduit un très efficace LongAdder accumulateur conçu pour accélérer la synthèse dans des flux parallèles et des environnements multi-threads. Ici, voici un exemple d'utilisation:

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

À partir des documents

Opérations de réduction Une opération de réduction (également appelée pli) prend une séquence d'éléments d'entrée et les combine en un seul résultat récapitulatif par application répétée d'une opération de combinaison, telle que trouver la somme ou le maximum d'un ensemble de nombres, ou accumuler des éléments dans une liste. Les classes de flux ont plusieurs formes d'opérations de réduction générales, appelées reduction () et collect (), ainsi que plusieurs formes de réduction spécialisées telles que sum (), max () ou count ().

Bien sûr, de telles opérations peuvent être facilement implémentées sous forme de simples boucles séquentielles, comme dans:

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

Cependant, il existe de bonnes raisons de préférer une opération de réduction à une accumulation mutative telle que la précédente. Non seulement une réduction est "plus abstraite" - elle opère sur le flux dans son ensemble plutôt que sur des éléments individuels - mais une opération de réduction correctement construite est intrinsèquement parallélisable, tant que la ou les fonctions utilisées pour traiter les éléments sont associatives. et apatrides. Par exemple, étant donné un flux de nombres dont nous voulons trouver la somme, nous pouvons écrire:

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

ou:

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

Ces opérations de réduction peuvent s'exécuter en toute sécurité en parallèle avec presque aucune modification:

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

Donc, pour une carte que vous utiliseriez:

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

Ou:

integers.values().stream().reduce(0, Integer::sum);
J Atkin
la source
2
Ce que le PO a est beaucoup mieux et aussi plus clair. Ce code impliquerait toute une série d'opérations de déballage et de boxe.
JB Nizet
1
@JBNizet Sauf si l'analyse d'échappement élimine la boxe. Il faudrait l'essayer pour voir si c'est possible.
Peter Lawrey
6
(x, y) -> x + y doit décompresser x et y, les additionner, puis encadrer le résultat. Et recommencez pour ajouter le résultat avec l'élément suivant du flux, et encore et encore.
JB Nizet
3
Integer :: sum souffre du même problème. Et si vous utilisez mapToInt () pour avoir un IntStream, appeler sum () dessus est plus simple que d'appeler Reduce ().
JB Nizet
3
Voir docs.oracle.com/javase/8/docs/api/java/lang/… . Les deux arguments de Integer.sum () sont de type int. Ainsi, les deux entiers du flux doivent être décompressés pour être passés en arguments à la méthode. La méthode retourne un int, mais Reduce () prend un BinaryOperator <Integer> comme argument, qui retourne donc un Integer. Le résultat de la somme doit donc être encadré en entier.
JB Nizet
28

Vous pouvez utiliser la méthode de réduction:

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

ou

long sum = result.stream().map(e -> e.getCreditAmount()).reduce(0L, Integer::sum);
Saeed Zarinfam
la source
9
Il y a déjà un tel accumulateur pour int, c'estInteger::sum
Alex Salauyou
1
Vous revenez longtemps, donc ce serait mieux Long::sumque Integer::sum.
Andrei Damian-Fekete
16

Vous pouvez utiliser reduce()pour additionner une liste d'entiers.

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

Vous pouvez utiliser la méthode collect pour ajouter une liste d'entiers.

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

Ce serait le moyen le plus court de résumer le inttableau de type (pour longtableau LongStream, pour doubletableau DoubleStream, etc.). Cependant, tous les types d'entiers primitifs ou à virgule flottante ne sont pas Streamimplémentés.

IntStream.of(integers).sum();
Sachith Dickwella
la source
Malheureusement, nous n'avons pas d'int-array. Donc IntStream.of(), ne fonctionnera pas pour ce problème, sauf si nous faisons quelque chose de fantasmagorique comme ceci:IntStream.of( integers.values().stream().mapToInt( Integer::intValue ).toArray() ).sum();
Kaplan
Pas besoin, ce serait suffisant integers.values().stream().mapToInt( Integer::intValue ).sum().
Sachith Dickwella
3

Que cela aide ceux qui ont des objets sur la liste.

Si vous avez une liste d'objets et que vous souhaitez additionner des champs spécifiques de cet objet, utilisez ce qui suit.

List<ResultSom> somList = MyUtil.getResultSom();
BigDecimal result= somList.stream().map(ResultSom::getNetto).reduce(
                                             BigDecimal.ZERO, BigDecimal::add);
itro
la source
Merci, cela m'a aidé dans l'un de mes scénarios
A_01
1

J'ai déclaré une liste d'entiers.

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

Vous pouvez essayer d'utiliser ces différentes méthodes ci-dessous.

En utilisant mapToInt

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

En utilisant summarizingInt

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

En utilisant reduce

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

La plupart des aspects sont couverts. Mais il pourrait être nécessaire de trouver l'agrégation d'autres types de données en dehors d'Integer, Long (pour lequel un support de flux spécialisé est déjà présent). Par exemple, stram avec BigInteger Pour un tel type, nous pouvons utiliser réduire l'opération comme

list.stream (). réduire ((bigInteger1, bigInteger2) -> bigInteger1.add (bigInteger2))

ManojG
la source