Guía de recopiladores de Java 8: averagingDouble(), averagingLong() y averagingInt()

En esta guía, aprenda a calcular el valor medio/promedio de colecciones/listas de elementos en Java recopilándolos con averagingInt(), averagingDouble() y averagingLong().

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.

La operación matemática de encontrar una media aritmética es una que usamos con bastante frecuencia, y hay muchas maneras de realizarla.

Haremos exactamente eso en esta guía: veremos cómo obtener un valor medio/promedio para diferentes tipos numéricos dentro de Java a través de métodos integrados dentro de la clase Collectors.

{.icon aria-hidden=“true”}

Nota: Vale la pena señalar que puede promediar los elementos mismos, si son numéricos, o reducirlos a un representación numérica y luego promediar 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 promediadores_()

La clase Collectors tiene una variedad de funciones útiles, y sucede que contiene algunas que nos permiten encontrar un valor medio de los elementos de entrada.

Hay un total de tres de ellos: Collectors.averagingInt(), Collectors.averagingDouble() y Collectors.averagingLong().

Empecemos echando un vistazo a las firmas de los métodos:

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

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

public static <T> Collector<T,?,Double> averagingLong(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.

ToIntFunction, ToDoubleFunction y ToLongFunction de java.util.function nos permiten realizar conversiones (reducciones) de tipos de objetos a sus campos primitivos int, double o long. Definamos una clase Estudiante que podamos reducir a un campo numérico:

1
2
3
4
5
6
7
public class Student {
    private String name;
    private Double grade;
    private Integer age;
    private Long examsTaken;

   // Constructor, getters and setters

Instanciamos también a nuestros estudiantes en una Lista:

1
2
3
4
5
6
7
List<Student> students = Arrays.asList(
    new Student("John", 7.56, 21, 17L),
    new Student("Jane", 8.31, 19, 9L),
    new Student("Michael", 9.17, 20, 14L),
    new Student("Danielle", 9.17, 21, 23L),
    new Student("Joe", 8.92, 22, 25L)
);

Además de los objetos personalizados, también veremos cómo podemos usar los recopiladores de promedios en tipos de datos primitivos, es decir, las “Listas” consisten solo en elementos numéricos.

Collectors.averagingInt()

Este método devuelve un Collector que produce la media aritmética de una función de valor entero aplicada a los elementos de entrada. En el caso de que no haya elementos presentes, el resultado es 0.

Dado que todos los métodos de promedio son bastante sencillos, iremos directamente a un ejemplo.

El primero de los dos ejemplos usará una ‘Lista’ simple compuesta de ‘Enteros’. Digamos que tenemos lo siguiente:

1
2
3
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
Double average = numbers.stream().collect(Collectors.averagingInt(Integer::intValue));
System.out.println(average);

Aplicamos el método .stream() para crear una secuencia de objetos Integer, después de lo cual usamos el método .collect() comentado anteriormente para recopilar la secuencia con el recopilador averagingInt().

Dado que los valores ya son enteros, obtenga el intValue a través de una referencia de método, realizando efectivamente un mapeo 1 a 1 como nuestra ToIntFunction, ya que no se requiere conversión:

1
3.0

A continuación, en nuestra clase ‘Estudiante’, el único campo de valor entero es ’edad’. En el siguiente ejemplo, calcularemos la edad promedio de todos nuestros estudiantes:

1
2
Double averageAge = students.stream().collect(Collectors.averagingInt(Student::getAge));
System.out.println("Average student age is: " + averageAge);

El funcionamiento interno cuando se usa un campo de una clase definida por el usuario es el mismo, la única diferencia es que no podemos promediar las instancias de “Estudiante”, por lo que las reducimos a su edad. La ToIntFunction en este caso es una referencia de método al método getAge() y, a través de ella, reducimos al Student a su edad.

Este proceso sigue siendo el mismo en nuestros dos próximos métodos, lo único que cambiará es a qué método nos referimos para las funciones de conversión. El resultado de nuestro fragmento de código es:

1
Average student age is: 20.6

Coleccionistas.averagingDouble()

Este método devuelve un Collector que produce la media aritmética de una función de doble valor aplicada a los elementos de entrada. En el caso de que no haya elementos presentes, el resultado es 0.

Collectors.averagingDouble() difiere un poco de averagingInt() dado que funciona con dobles. El promedio que se devuelve puede variar según el orden en que se procesan los valores, debido a los errores de redondeo acumulados. Los valores ordenados por orden creciente tienden a producir resultados más precisos.

Si algún valor es NaN o la suma en cualquier punto es NaN, la media también será NaN. Además, el formato doble se puede representar con todos los enteros consecutivos en el rango de -2^53^ a 2^53^.

Calculemos el valor medio de una lista de dobles:

1
2
3
List<Double> numbers = Arrays.asList(3.0, 8.0, 4.0, 11.0);
Double average = numbers.stream().collect(Collectors.averagingDouble(Double::doubleValue));
System.out.println(average);

Esto resulta en:

1
6.5

Ahora, apliquemos el método a nuestra clase Student. El campo calificación es un doble, por lo que usaremos una referencia de método al captador de ese campo como ToDoubleFunction en la llamada averagingDouble():

1
2
Double averageGrade = students.stream().collect(Collectors.averagingDouble(Student::getGrade));
System.out.println("Average student grade is: " + averageGrade);

Ejecutar esto nos da el siguiente resultado:

1
Average student grade is: 8.62

Coleccionistas.averagingLong()

El último de los métodos de promedio es Collectors.averagingLong(). Este método, al igual que los dos anteriores, devuelve un Collector que produce la media aritmética de una función de valor largo aplicada a los elementos de entrada. Si no hay promedio de elementos, se devuelve 0.

Al igual que con los dos anteriores, podemos promediar fácilmente una lista de valores largos:

1
2
3
List<Long> numbers = Arrays.asList(10L, 15L, 1L, 3L, 7L);
Double average = numbers.stream().collect(Collectors.averagingDouble(Long::longValue));
System.out.println(average);

Esto resulta en:

1
7.2

Finalmente, nuestra clase ‘Estudiante’ tiene un campo ’examen realizado’ de valor largo, para el cual podemos calcular la media de. Usaremos una referencia de método al método getExamsTaken() como ToLongFunction:

1
2
3
Double averageExamsTaken = students.stream().
    collect(Collectors.averagingLong(Student::getExamsTaken));
System.out.println("Average exam taken per student is: " + averageExamsTaken);

Ejecutando estas salidas:

1
Average exam taken per student is: 17.6

Conclusión

Muchas veces tenemos la necesidad de calcular promedios de múltiples valores, y usar los métodos averaging_() provistos de la clase Collectors es una de las formas más fáciles y eficientes de hacer esto en Java.

En esta guía, revisamos los tres disponibles dentro de la clase mencionada anteriormente, explicamos cómo funciona cada uno de ellos en un ejemplo de una clase definida por el usuario y mostramos cómo podemos usarlos en el código para lograr los resultados que estábamos buscando. apuntando hacia. o hacia.