Guía de recopiladores de Java 8: summingDouble(), summingLong() y summingInt()

En esta guía de Java 8 Streams, veremos los recopiladores de suma para enteros, dobles y largos en Java, con ejemplos prácticos y consejos.

Introducción

Una secuencia representa una secuencia de elementos y admite diferentes tipos de operaciones que conducen al resultado deseado. La fuente de un flujo suele ser una Colección o un Array, desde el cual se transmiten los datos.

Los flujos se diferencian de las colecciones en varios aspectos; sobre todo porque los flujos no son una estructura de datos que almacena elementos. Son de naturaleza funcional, y vale la pena señalar que las operaciones en un flujo producen un resultado y, por lo general, devuelven otro flujo, pero no modifican su origen.

Para "solidificar" los cambios, reúne los elementos de un flujo en una Colección.

Muchas operaciones y conceptos matemáticos simples encuentran su uso en la programación, y la mayoría de las veces estos son tan sencillos en la programación como lo son en las matemáticas mismas. Resumir elementos no es indiferente a esto, y su uso a menudo se pasa por alto porque hay muchas formas de utilizar esta tarea simple.

En esta guía, veremos cómo sumar todos los elementos en una colección en Java, usando summingDouble(), summingLong() y summingInt().

{.icon aria-hidden=“true”}

Nota: Vale la pena señalar que puede sumar los elementos mismos, si son sumables, o reducirlos a un representación numérica y luego sumar las reducciones, si no lo son.

Coleccionistas y Stream.collect()

Los recopiladores representan implementaciones de la interfaz Collector, que implementa varias operaciones de reducción útiles, como acumular elementos en colecciones, resumir elementos en función de un parámetro específico, etc.

Todas las implementaciones predefinidas se pueden encontrar dentro de la clase Collectors.

Sin embargo, también puede implementar muy fácilmente su propio recopilador y usarlo en lugar de los predefinidos; puede llegar bastante lejos con los recopiladores integrados, ya que cubren la gran mayoría de los casos en los que es posible que desee usarlos.

Para poder usar la clase en nuestro código necesitamos importarla:

1
import static java.util.stream.Collectors.*;

Stream.collect() realiza una operación de reducción mutable en los elementos de la secuencia.

{.icon aria-hidden=“true”}

Una operación de reducción mutable recopila elementos de entrada en un contenedor mutable, como una Colección, mientras procesa los elementos de la secuencia.

Definición de los recopiladores suming_()

Dentro de la propia clase Collectors, encontramos una gran cantidad de métodos únicos que satisfacen las diferentes necesidades de un usuario. Uno de estos grupos está formado por métodos sumar - summingInt(), summingDouble() y summingLong().

Aunque cada uno de estos métodos funciona para un tipo de datos distintivo enumerado en sus nombres, todos funcionan de manera bastante similar, con pequeñas diferencias:

1
2
3
4
5
public static <T> Collector<T,?,Integer> summingInt(ToIntFunction<? super T> mapper)

public static <T> Collector<T,?,Double> summingDouble(ToDoubleFunction<? super T> mapper)

public static <T> Collector<T,?,Long> summingLong(ToLongFunction<? super T> mapper)

{.icon aria-hidden=“true”}

Nota: La T genérica en las firmas del método representa el tipo de elementos de entrada con los que estamos trabajando.

Anteriormente dijimos que estos métodos solo se pueden usar para entradas numéricas. Las ToIntFunction, ToDoubleFunction y ToLongFunction predefinidas de java.util.function nos permiten hacer exactamente tales conversiones, desde tipos de objetos a sus tipos primitivos int, double, long.

Antes de comenzar a sumergirnos en cada uno de los métodos individuales, definiremos una clase que usaremos para nuestros ejemplos junto con algunos campos.

Esta clase se llamará Producto:

1
2
3
4
5
6
7
8
public class Product {
    private String name;
    private Integer quantity;
    private Double price;
    private Long productNumber;

    // Constructor, getters and setters
}

La clase tiene cuatro campos de diferentes tipos:

  • String name - no terminaremos usando este campo ya que este es un valor no numérico, pero para tener ejemplos significativos necesitamos nombrar nuestros productos.
  • Cantidad entera - el número de productos en nuestro inventario.
  • Precio doble - el precio del producto.
  • Número de producto largo: un código de seis dígitos para cada uno de nuestros productos.

Junto a estos cuatro campos, también tenemos un constructor simple y captadores para todo excepto el nombre del producto en sí. Instancemos también nuestra Lista de productos en el programa principal:

1
2
3
4
5
6
7
List<Product> products = Arrays.asList(
        new Product("Milk", 37, 3.60, 12345600L),
        new Product("Carton of Eggs", 50, 1.20, 12378300L),
        new Product("Olive oil", 28, 37.0, 13412300L),
        new Product("Peanut butter", 33, 4.19, 15121200L),
        new Product("Bag of rice", 26, 1.70, 21401265L)
);

{.icon aria-hidden=“true”}

Nota: Además de esta clase, que tendremos que reducir a un campo para sumar, también veremos ejemplos de Listas que consisten solo en Entero , elementos Doble y Largo.

Coleccionistas.sumingInt()

El método summingInt() devuelve un Collector que produce la suma de una función de valor entero aplicada a los elementos de entrada. En otras palabras, suma los enteros de la colección y devuelve el resultado. En el caso de que no haya elementos de entrada, el valor devuelto es 0.

Empecemos con un ejemplo básico con una Lista de Enteros:

1
2
3
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
Integer sum = numbers.stream().collect(Collectors.summingInt(Integer::intValue));
System.out.println("Sum: " + sum);

Aplicamos el método .stream() para crear un flujo de instancias Integer, después de lo cual usamos el método .collect() discutido anteriormente para recolectar los elementos usando summingInt(). El método en sí, nuevamente, acepta ToIntFunction, que se puede usar para reducir las instancias a un número entero que se puede sumar.

Como ya estamos usando números enteros, simplemente podemos pasar una referencia de método que indique su intValue, ya que no se necesita ninguna reducción adicional:

1
Sum: 15

La mayoría de las veces, trabajará con listas de objetos personalizados y le gustaría sumar algunos de sus campos. Por ejemplo, podemos sumar las cantidades de cada producto en productList, denotando el inventario total que tenemos.

En tal caso, podemos usar una referencia de método, como Product::getQuantity como nuestra ToIntFunction, para reducir los objetos a un solo entero cada uno, y luego sumar estos enteros:

1
2
Integer sumOfQuantities = products.stream().collect(Collectors.summingInt(Product::getQuantity));
System.out.println("Total number of products: " + sumOfQuantities);

Esto resulta en:

1
Total number of products: 174

Si desea obtener más información sobre la reducción y las operaciones de reducción, que suelen ser el núcleo de las operaciones de suma y promedio, entre otras tareas, lea nuestra [Java 8 Streams: Guía definitiva para reducir()](/java- 8-guia-de-flujos-para-reducir/)!

Coleccionistas.sumingDouble()

De la misma manera summingInt() devuelve el resultado de la suma de valores enteros - summingDouble() devuelve el resultado de la suma de valores dobles.

Sin embargo, este método difiere de summingInt() en un aspecto. La suma devuelta puede variar según el orden en que se registren los valores, debido a errores de redondeo acumulados. Los valores clasificados por orden creciente tienden a producir resultados más precisos.

{.icon aria-hidden=“true”}

Nota: Si cualquier valor es NaN o la suma en cualquier punto es NaN, el resultado también será NaN.

Comencemos con una lista de dobles:

1
2
3
List<Double> numbers = Arrays.asList(3.0, 5.5, 11.3, 40.3, 21.1);
Double sum = numbers.stream().collect(Collectors.summingDouble(Double::doubleValue));
System.out.println(sum);

Después de redondear nuestra salida, se imprime:

1
81.2

Una vez más, normalmente trabajamos con objetos personalizados y no con tipos envolventes/primitivos. Los precios de nuestros productos están expresados ​​en dobles, por lo que podríamos sumar precios. Si tuviéramos que comprar una sola instancia de cada producto en el inventario, ¿cuál sería el precio?

Usemos summingDouble() para obtener una cotización del precio:

1
2
Double sumOfPrices = products.stream().collect(Collectors.summingDouble(Product::getPrice));
System.out.println("The total sum of prices: " + sumOfPrices);

Esto nos da:

1
The total sum of prices: 47.69

Si quisiéramos ser un poco creativos y contar el precio total de todos nuestros productos combinados, también podemos hacerlo usando este método. Sin embargo, requiere cálculos previos que lo hacen significativamente más fácil:

1
2
3
4
5
6
7
List<Double> productPrices = new ArrayList<>();
for(Product product : products){
    productPrices.add(product.getPrice() * product.getQuantity());
}

Double sumOfPrices = productPrices.stream().collect(Collectors.summingDouble(Double::doubleValue));
System.out.println("Sum of all product prices : " + sumOfPrices);

Ejecutar este código nos da lo siguiente:

1
Sum of all product prices : 1411.67

Coleccionistas.summingLong()

El tercer y último método del grupo de métodos suming es summingLong(). Este método, como los dos anteriores, devuelve un Collector que produce la suma de una función de valor largo aplicada a los elementos de entrada. Si no hay elementos presentes, el resultado es 0:

1
2
3
List<Long> numbers = Arrays.asList(23L, 11L, 13L, 49L, 7L);
Long sum = numbers.stream().collect(Collectors.summingLong(Long::longValue));
System.out.println(sum);

Esto da como resultado:

1
103

Finalmente, nuestro campo productNumber es de tipo Largo. Resulta que los números se eligieron cuidadosamente para codificar un mensaje secreto una vez divididos y convertidos de decimal a ASCII. Sumamos los largos y escribimos una función auxiliar personalizada para decodificar el mensaje:

1
2
3
Long productNumbers = products.stream().collect(Collectors.summingLong(Product::getProductNumber));
System.out.println(productNumbers);
System.out.println(decodeMessage(productNumbers));

El método decodeMessage() toma un Largo, lo divide y considera cada dos caracteres como una representación decimal de un Carácter, antes de agregar los caracteres y devolver el mensaje:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
public static String decodeMessage(Long encodedMessage) {
    String message = String.valueOf(encodedMessage);
    String[] characters = message.split("");
    StringBuilder result = new StringBuilder();

    for (int i = 1; i < characters.length; i+=2) {
        result.append(Character.toString(Integer.parseInt(characters[i-1]+characters[i])));
    }

    return result.toString();
}

Impresionante, echemos un vistazo al resultado:

1
2
74658665
JAVA

No es un emocionante mensaje secreto, por supuesto, pero un mensaje al fin y al cabo.

Conclusión

En esta guía, echamos un vistazo a los recopiladores summing y exploramos los métodos summingInt(), summingDouble() y summingLong().

Hemos explorado su uso en contenedores primitivos, así como en objetos personalizados, que normalmente se reducen a un campo para operaciones de suma.