¿Cómo sumar una lista de enteros con flujos de Java?

365

Quiero resumir una lista de enteros. Funciona de la siguiente manera, pero la sintaxis no se siente bien. ¿Se podría optimizar el código?

Map<String, Integer> integers;
integers.values().stream().mapToInt(i -> i).sum();
miembros
fuente
55
"pero la sintaxis no se siente bien" ¿Qué te hace pensar eso? Este es el idioma habitual. Tal vez quiera usar mapToLongpara evitar desbordamientos, dependiendo de los valores que pueda tener su mapa.
Alexis C.
3
@JBNizet Me parece i -> imuy claro, personalmente. Bueno, sí, necesita saber que el valor se desempaquetará automáticamente, pero es cierto desde Java 5 ...
Alexis C.
44
@AlexisC. es comprensible porque se pasó a mapToInt () y porque soy un desarrollador experimentado. Pero i -> i, sin contexto, parece un noop. Integer :: intValue es más detallado, pero hace explícita la operación de unboxing.
JB Nizet
1
@JBNizet Las personas que llaman al método foo(int i)no escriben foo(myInteger.intValue());cada vez que lo llaman (¡o al menos espero que no!). Estoy de acuerdo con usted en que Integer::intValuees más explícito, pero creo que lo mismo se aplica aquí. La gente debería aprenderlo una vez y listo :-). No es como si fuera una ofuscación mágica.
Alexis C.
44
@JB Nizet: bueno, i -> iparece un no-op y, conceptualmente, es un no-op. Claro, bajo el capó Integer.intValue()se llama, pero aún más profundo bajo el capó, esos métodos se alinean para convertirse exactamente en el no-op que parece en el código fuente. Integer::intValuetiene el punto extra de no crear un método sintético en el código de bytes, pero no es lo que debe impulsar su decisión de cómo organizar su código fuente.
Holger

Respuestas:

499

Esto funcionará, pero i -> iestá haciendo un unboxing automático, por lo que "se siente" extraño. Cualquiera de los siguientes funcionará y explicará mejor lo que el compilador está haciendo bajo el capó con su sintaxis original:

integers.values().stream().mapToInt(i -> i.intValue()).sum();
integers.values().stream().mapToInt(Integer::intValue).sum();
Necreaux
fuente
2
¿Qué pasa si tenemos un BigInteger :)?
GOXR3PLUS
13
Una opción simple esBigDecimal sum = numbers.stream().reduce(BigDecimal.ZERO, BigDecimal::add);
Matthew
158

Sugiero 2 opciones más:

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

El segundo usa el Collectors.summingInt()colector, también hay un summingLong()colector con el que usarías mapToLong.


Y una tercera opción: Java 8 presenta un LongAdderacumulador muy eficaz diseñado para acelerar el resumen en flujos paralelos y entornos de subprocesos múltiples. Aquí, aquí hay un ejemplo de uso:

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

De los documentos

Operaciones de reducción Una operación de reducción (también llamada pliegue) toma una secuencia de elementos de entrada y los combina en un único resultado de resumen mediante la aplicación repetida de una operación de combinación, como encontrar la suma o el máximo de un conjunto de números, o acumular elementos en una lista. Las clases de secuencias tienen múltiples formas de operaciones de reducción generales, llamadas reduce () y collect (), así como múltiples formas de reducción especializadas como sum (), max () o count ().

Por supuesto, tales operaciones pueden implementarse fácilmente como simples bucles secuenciales, como en:

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

Sin embargo, hay buenas razones para preferir una operación de reducción sobre una acumulación mutativa como la anterior. Una reducción no solo es "más abstracta": opera en la secuencia como un todo en lugar de elementos individuales, sino que una operación de reducción construida adecuadamente es inherentemente paralelizable, siempre que las funciones utilizadas para procesar los elementos sean asociativas y apátrida. Por ejemplo, dada una secuencia de números para los que queremos encontrar la suma, podemos escribir:

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

o:

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

Estas operaciones de reducción pueden ejecutarse de forma segura en paralelo casi sin modificaciones:

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

Entonces, para un mapa usarías:

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

O:

integers.values().stream().reduce(0, Integer::sum);
J Atkin
fuente
2
Lo que tiene el OP es mucho mejor y también más claro. Este código involucraría una gran cantidad de operaciones de unboxing y boxeo.
JB Nizet
1
@JBNizet A menos que el análisis de escape elimine el boxeo. Tendría que probarlo para ver si puede.
Peter Lawrey
66
(x, y) -> x + y necesita desempaquetar x e y, sumarlos y luego encuadrar el resultado. Y comience nuevamente para agregar el resultado con el siguiente elemento de la secuencia, y una y otra vez.
JB Nizet
3
Integer :: sum sufre el mismo problema. Y si usa mapToInt () para tener un IntStream, llamar a sum () es más sencillo que llamar a reduce ().
JB Nizet
3
Ver docs.oracle.com/javase/8/docs/api/java/lang/… . Los dos argumentos de Integer.sum () son de tipo int. Por lo tanto, los dos enteros de la secuencia deben estar sin caja para pasarlos como argumentos al método. El método devuelve un int, pero reduce () toma un BinaryOperator <Integer> como argumento, lo que devuelve un Integer. Por lo tanto, el resultado de la suma debe estar encuadrado en Integer.
JB Nizet
28

Puede usar el método de reducción:

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

o

long sum = result.stream().map(e -> e.getCreditAmount()).reduce(0L, Integer::sum);
Saeed Zarinfam
fuente
99
Ya existe ese acumulador para int, esInteger::sum
Alex Salauyou
1
Estás regresando mucho, así que sería mejor Long::sumque Integer::sum.
Andrei Damian-Fekete
16

Puede usar reduce()para sumar una lista de enteros.

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

Puede usar el método de recopilación para agregar una lista de enteros.

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

Esta sería la forma más corta de resumir el inttipo de matriz (para longmatriz LongStream, para doublematriz, DoubleStreametc.). Sin Streamembargo, no todos los tipos primitivos de entero o coma flotante tienen la implementación.

IntStream.of(integers).sum();
Sachith Dickwella
fuente
Lamentablemente no tenemos ningún int-array. Por IntStream.of()lo tanto , no funcionará para este problema, a menos que estemos creando algo espeluznante como este:IntStream.of( integers.values().stream().mapToInt( Integer::intValue ).toArray() ).sum();
Kaplan
No es necesario, esto sería suficiente integers.values().stream().mapToInt( Integer::intValue ).sum().
Sachith Dickwella
3

Que esto ayude a aquellos que tienen objetos en la lista.

Si tiene una lista de objetos y desea sumar campos específicos de este objeto, use lo siguiente.

List<ResultSom> somList = MyUtil.getResultSom();
BigDecimal result= somList.stream().map(ResultSom::getNetto).reduce(
                                             BigDecimal.ZERO, BigDecimal::add);
itro
fuente
Gracias Me ayudó en una de mis scenerio
A_01
1

He declarado una lista de enteros.

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

Puede intentar usar estas diferentes formas a continuación.

Utilizando mapToInt

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

Utilizando summarizingInt

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

Utilizando reduce

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

La mayoría de los aspectos están cubiertos. Pero podría haber un requisito para encontrar la agregación de otros tipos de datos aparte de Integer, Long (para el cual ya está presente el soporte de flujo especializado). Por ejemplo, Stram con BigInteger. Para este tipo, podemos utilizar la operación de reducción como

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

ManojG
fuente