Skip to main content

Sumar una lista de números en Java

Escrito por
Java engineering feature

22 de mayo de 2020

0 minutos de lectura

De vez en cuando, necesito hacer algunas tareas básicas en Java y me pregunto cuál es la mejor manera de hacerlo. ¡Me pasó hace unos días! Solo necesitaba obtener la suma de una lista de números y descubrí que hay varias maneras —nunca mejor dicho— de hacerlo.

El enfoque tradicional

Podemos crear un ciclo sencillo para hacerlo. Estoy usando Java 11, así que discúlpame si usas, por ejemplo, Java 8, y List.of y var no funcionan en tu caso. De todos modos, creo que entenderás la idea.

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);
}

Obviamente, desde Java 5 tenemos mejoras en los ciclos for, así que puedo reescribir el mismo código de esta manera.

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

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

La diferencia es sutil. Sin embargo, el código ya es más expresivo, pues dice algo como: «de cada number que viene de listOfNumbers, quiero hacer lo siguiente...».

El enfoque de Java Stream

Quienes me conocen saben que, durante mis años universitarios, me lavaron el cerebro con la programación en Haskell. Esto significa que me encanta la programación funcional pura. No es que Java pueda manejarla ?, pero la expresividad de la programación funcional está disponible, en cierta medida, mediante la API de streams.

Con la API de streams de Java, podemos ejecutar el modelo de programación MapReduce. Para el problema que intento resolver aquí, no necesito aplicar map, ya que los números seguirán siendo los mismos. Sin embargo, sí tengo que reducir la lista a un solo número: la suma.

Collect

Probablemente, en el 99 % de los casos usamos la función collect con el colector estándar toList() para volver a convertir nuestro stream en una lista. Algo así:

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

Sin embargo, hay mucho más que hacer con un stream que volver a recopilarlo en una lista. Si exploras la biblioteca Collectors, encontrarás funciones como summingInt(), summingDouble() y summingLong(). Puedes usar estas funciones para recopilar (o reducir) la lista y obtener la suma.

La función summmingInt requiere una función que convierta la entrada que tienes en un int. En este caso, puedo usar simplemente la «función identidad». La función i -> i es suficiente.

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

Esta función identidad puede parecer absurda, así que puedes usar Integer.intValue() en su lugar.

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

Cuando hago esto, mi IDE —IntelliJ IDEA, en mi caso— me recomienda refactorizar el código y usar la función mapToInt(), como se muestra a continuación:

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

En términos técnicos, lo que hacemos aquí es convertir cada elemento en un int, que ya es lo que es ¯\(ツ)/¯, ¿verdad?, y reducirlo con la función sum().

Se entiende mejor si observas los tipos inferidos. Simplemente no puedes tener una lista de primitivos. Por eso, la lista contiene elementos Integer (el objeto). Esto significa que cada elemento de la lista debe volver al tipo primitivo int para que sea posible usar sum(). El ejemplo anterior con la función identidad en el colector funciona gracias al desempaquetado automático de Java.

Si prefieres usar listas de tipos primitivos en Java, te recomiendo echar un vistazo a la biblioteca Eclipse Collections.

Reduce

En Java, la reducción se logra con un par de funciones de la API de streams. Además de collect(), también está la función, llamada muy apropiadamente, 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);

En este caso, la función reduce recibe un punto de partida y una expresión lambda BiFunction. La BiFunction se aplica al punto de partida y al primer número; luego, el resultado de la función se aplica al segundo número, y así sucesivamente.

El código anterior hace algo como esto: 0 + 1, 1 + 2, 3 + 3, 6 + 4, etcétera…

Ahora puedes omitir el punto de partida 0. Sin embargo, en este caso, la función reduce devolverá un Optional, ya que la lista que intenta reducir podría estar vacía.

Conclusión

Como puedes ver, hay varias maneras de resolver este problema. Sin duda, se les ocurrirán formas aún más exóticas de hacerlo. Mi opción favorita es reduce(). Para mí, es la solución más expresiva y pura en Java. Simplemente quiero reducir una lista a un solo número, sin tener que preocuparme por las transformaciones de tipos envoltorio a primitivos. Además, puedo reutilizar este enfoque cuando necesite reducir una lista de otros tipos, escribiendo una función lambda de reducción que se ajuste a mis necesidades.

Publicado en: