¿Cómo calcular el máximo y mínimo en Java usando Streams?

¿Cómo calcular el máximo y mínimo en Java usando Streams?


JavaStreamsComparator

En este post vamos a ver cómo usar la API de Streams de Java para calcular el máximo y mínimo de una colección de elementos. Para ello, vamos a usar los métodos max() y min() de la clase Stream. Estos métodos nos permiten obtener el máximo y mínimo de una colección de elementos, respectivamente, utilizando un comparador personalizado.

Supongamos que tenemos una lista del tipo List<Exam> donde Exam es un record con dos propiedades name y grade, y queremos obtener el examen con la nota más alta y la nota más baja.

public record Exam(String name, Double grade) { }
var exams = Arrays.asList(
        new Exam("Alice", 9.5),
        new Exam("Bob", 6.0),
        new Exam("Charlie", 7.5),
        new Exam("David", 8.0),
        new Exam("Eve", 8.5)
);

En versiones recientes de Java se puede utilizar el método max() y min() de la clase Stream para este propósito pasando un comparador personalizado y obteniendo un Optional<T> con el resultado. En caso de que la versión de Java no sea compatible con los métodos max() y min(), se puede utilizar el método collect() pasando como parámetro un Collector, ya sea Collectors.maxBy() o Collectors.minBy(), y a su vez un comparador personalizado. En caso de utilizar esta última opción en una versión de Java reciente el propio IDE nos sugerirá cambiarlo por el método max() o min().

Comencemos creando un comparador personalizado del tipo Comparator<Exam> que nos permita obtener las calificaciones en orden ascendente.

Comparator<Exam> byGradeAsc = (e1, e2) -> (int) (e1.grade() - e2.grade());

A manera de ejemplo, este comparador devolverá un número negativo si la calificación del primer examen es menor que la del segundo, cero si son iguales y un número positivo si la calificación del primer examen es mayor que la del segundo, para más información puedes consultar este enlace a un post donde se explica cómo funciona la interfaz Comparator.

Este comparador se puede definir de otra manera utilizando el método comparingDouble() de la clase Comparator que nos permite crear un comparador a partir de una función que extrae un valor doble de un objeto sin la necesidad de escribir el comparador manualmente.

Comparator<Exam> byGradeAscClean = Comparator.comparingDouble(Exam::grade);

En esta definición se le indica al comparador que debe de tomar un objeto del tipo Exam, extraer el valor de la propiedad grade y comparar los valores utilizando un tipo de dato Double.

Estas dos maneras son equivalentes y se pueden utilizar indistintamente, puede que la segunda sea un poco más difícil de entender al principio, pero una vez que se entiende es más fácil de escribir y leer.

Ahora que se tiene el comparador, solo resta utilizarlo en los métodos max() y min() para obtener el examen con la calificación más alta y más baja, respectivamente.

var highestGrade = exams.stream().max(byGradeAscClean);
var lowestGrade = exams.stream().min(byGradeAscClean);

En este caso, se obtiene un Optional<Exam> con el examen con la calificación más alta y más baja, respectivamente. En caso de que la lista de exámenes esté vacía, se obtendrá un Optional.empty(). Para obtener el valor del Optional se puede utilizar el método ifPresent() que recibe un Consumer y ejecuta el código que se le pase como argumento si existe un valor.

highestGrade.ifPresent(ex -> System.out.println("Highest grade: " + ex.name() + " " + ex.grade()));
lowestGrade.ifPresent(ex -> System.out.println("Lowest grade: " + ex.name() + " " + ex.grade()));

// Output
Highest grade: Alice 9.5
Lowest grade: Bob 6.0

O simplemente pasar como argumento un método de referencia para imprimir el valor del Optional, en caso de que él Optional esté vacío no se imprimirá nada, caso contrario se imprimirá el valor del Optional llamando al método toString() del objeto Exam.

highestGrade.ifPresent(System.out::println);
lowestGrade.ifPresent(System.out::println);

// Output
Exam[name=Alice, grade=9.5]
Exam[name=Bob, grade=6.0]

Listo, de esta manera se puede obtener el máximo y mínimo de una colección de elementos utilizando la API de Streams de Java y la interfaz Comparator.