Cómo obtener el elemento máximo o mínimo en la colección de Java

En esta guía, repasaremos cómo encontrar los elementos máximo y mínimo de una colección de Java utilizando comparadores predeterminados y personalizados, el marco de colecciones y la API de flujo de Java 8.

Introducción

En esta guía, veremos cómo obtener el elemento máximo o mínimo en una colección de Java, tanto para tipos primitivos como para objetos comparables personalizados, a través de sus campos.

Obtención del elemento máximo o mínimo con Collections.max()

El marco Collections nos proporciona una amplia variedad de métodos estáticos auxiliares para trabajar con Collections en Java. No es de extrañar que este mismo marco nos permita buscar y devolver el elemento máximo o mínimo de una colección también.

Collections.max() y Collections.min() con tipos primitivos

Trabajar con tipos primitivos es bastante fácil en la mayoría de los aspectos, y siempre que sean ‘Comparables’, podemos buscarlos fácilmente.

Para encontrar el elemento máximo o mínimo de una Colección que consta de tipos primitivos, simplemente llamamos a los métodos Collections.max() o Collections.min(), pasando las colecciones en las que estamos buscando:

1
2
3
4
5
6
7
List<Integer> list = List.of(1, 5, 4, 3, 7, 6, 9, 4);
        
Integer min = Collections.min(list);
Integer max = Collections.max(list);

System.out.println(String.format("Minimum element is %s", min));
System.out.println(String.format("Maximum element is %s", max));

Ejecutar este código devuelve nuestros elementos máximo y mínimo:

1
2
Minimum element is 1
Maximum element is 9

Collections.max() y Collections.min() con objetos personalizados

Sin embargo, rara vez solo trabajamos con tipos primitivos. Por lo general, estaremos trabajando con objetos. Naturalmente, dado que estas estructuras son mucho más complejas, puedes decidir qué constituye un elemento mayor entre los dos.

Por lo general, esto se logra implementando la interfaz Comparable, que le permite comparar dos instancias cualesquiera de una clase para determinar cuál es mayor. Definamos una clase y hagámosla Comparable:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
public class Book implements Comparable<Book> {
    private String author;
    private String name;
    private int pageNumber;

    // Constructor, getters and setters

    @Override
    public int compareTo(Book book) {
        return (this.pageNumber > book.pageNumber) ? 1 : -1;
    }

    @Override
    public String toString() {
        return "Book{" +
                "author='" + author + '\'' +
                ", name='" + name + '\'' +
                ", pageNumber=" + pageNumber +
                '}';
    }
}

Tendrás que @Override el método compareTo() y definir con qué criterios se comparan las entidades. Por lo general, al final se reducirá a tipos primitivos simples, como comparar el atributo pageNumber. También hemos agregado un método toString() para un formateo conveniente.

Ahora, buscar el elemento máximo o mínimo en una colección de objetos personalizados es tan fácil como llamar a Collections.max() o Collections.min() en la colección:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
List<Book> bookList = new ArrayList<>();
bookList.add(new Book("Nick Bostrom", "Superintelligence", 352));
bookList.add(new Book("Ray Kurzweil", "The Singularity is Near", 652));
bookList.add(new Book("Max Tegmark", "Our Mathematical Universe", 432));

Book min = Collections.min(bookList);
Book max = Collections.max(bookList);

System.out.println(String.format("Minimum element is %s", min));
System.out.println(String.format("Maximum element is %s", max));

Dados nuestros criterios de comparación, los resultados son:

1
2
Minimum element is Book{author='Nick Bostrom', name='Superintelligence', pageNumber=352}
Maximum element is Book{author='Ray Kurzweil', name='The Singularity is Near', pageNumber=652}

Comparador personalizado

Puede evitar hacer que el Libro sea comparable, si no puede, proporcionando un nuevo Comparador en las llamadas Colecciones. Sin embargo, esta solución es detallada y es mejor evitarla/sustituirla con las técnicas descritas a continuación.

Sin embargo, es una forma totalmente válida de comparar entidades y encontrar el valor máximo/mínimo:

1
2
3
4
5
6
7
8
Book min = Collections.min(bookList, new Comparator<Book>() {
    @Override
    public int compare(Book o1, Book o2) {
        return (o1.getPageNumber() > o2.getPageNumber()) ? 1 : -1;
    }
});

System.out.println(String.format("Minimum by page count is %s", min));

O bien, esto se puede acortar a través de una expresión lambda:

1
2
Book min = Collections.min(bookList, 
    (o1, o2) -> (o1.getPageNumber() > o2.getPageNumber()) ? 1 : -1);

Obtención del elemento máximo o mínimo con Stream.max() y Stream.min()

Con la llegada de Java 8, se nos presentó una maravillosa Stream API que nos permite realizar varias canalizaciones de procesamiento. Un ‘Stream’ puede encontrar un elemento máximo o mínimo a través de los métodos ‘max()’ o ‘min()’, aprovechando un nuevo ‘Comparador’ para el trabajo, o usando uno ya existente, como el comparador we\ hemos integrado en nuestra clase Book.

Esto le permite tener clases no comparables y usar rápidamente un nuevo comparador para establecer los criterios usando cualquier campo. Esta flexibilidad es lo que hace que Streams sea tan increíble para procesar datos.

Stream.max() y Stream.min() con tipos primitivos

Comencemos con una colección simple de tipos primitivos. Para obtener el elemento máximo o mínimo de la colección, lo stream() y llamamos a los métodos min() o max():

1
2
3
4
5
6
List<Integer> list = List.of(1, 5, 4, 3, 7, 6, 9, 4);
Integer maxInt = list.stream().mapToInt(i -> i).max().getAsInt();
Integer minInt = list.stream().mapToInt(i -> i).min().getAsInt();

System.out.println(String.format("Minimum element is %s", minInt));
System.out.println(String.format("Maximum element is %s", maxInt));

En lugar de mapear i -> i, podríamos haber usado alternativamente:

1
Integer maxInt = list.stream().mapToInt(Integer::intValue).max().getAsInt();

Los métodos max() y min() devuelven un Optional - o un derivado de la clase, como OptionalInt, OptionalDouble, etc. Para extraer el valor entero, usamos getAsInt( ) al final de la cadena de llamada.

Ejecutar este código da como resultado:

1
2
Minimum element is 1
Maximum element is 9

En lugar de mapToInt(), también puede usar un Comparador, como comparing() o comparingInt() (ambos producirían el mismo resultado):

1
2
Integer maxInt = list.stream().max(Comparator.comparing(Integer::intValue)).get();
Integer minInt = list.stream().min(Comparator.comparingInt(Integer::intValue)).get();

De nuevo, estos métodos devuelven un ‘Opcional’, por lo que ‘obtenemos()’ el resultado al final. comparing() también tiene la flexibilidad de comparar valores que no son enteros, pero dado que aquí nos estamos restringiendo a solo números enteros, no hay ninguna diferencia.

Stream.max() y Stream.min() con objetos personalizados

El uso de comparadores personalizados con objetos personalizados es donde los Streams brillan más y donde brindan la mayor flexibilidad. Cuando usamos el marco Collections, nos vimos obligados a comparar los elementos a través del método compareTo(), anulado desde la interfaz Comparable, si no desea definir un nuevo comparador voluminoso.

Con Streams, podemos definir un nuevo ‘Comparador’ sobre la marcha con cualquier campo, sin que la clase tenga que implementar la interfaz ‘Comparable’. Encontremos el elemento máximo y mínimo de una colección con objetos personalizados, usando un comparador personalizado y flujos:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
List<Book> bookList = new ArrayList<>();
bookList.add(new Book("Nick Bostrom", "Superintelligence", 352));
bookList.add(new Book("Ray Kurzweil", "The Singularity is Near", 652));
bookList.add(new Book("Max Tegmark", "Our Mathematical Universe", 432));

// Using native compareTo() Method
Book min = bookList.stream().min(Book::compareTo).get();
Book max = bookList.stream().max(Book::compareTo).get();

// Using custom new Comparator
Book minByAuthor = bookList.stream().min(Comparator.comparing(Book::getAuthor)).get();
Book maxByAuthor = bookList.stream().max(Comparator.comparing(Book::getAuthor)).get();

System.out.println(String.format("Minimum by page count is %s", min));
System.out.println(String.format("Maximum by page count is %s", max));

System.out.println(String.format("Minimum by author is %s", minByAuthor));
System.out.println(String.format("Maximum by author is %s", maxByAuthor));

Esta vez, podemos usar cualquier campo y suministrarlo al método Comparator.comparing(). También puedes usar métodos alternativos, como comparingInt() aquí, pero como estamos comparando el valor lexicográfico de Strings, nos quedaremos con el método genérico comparing().

Ejecutar este código da como resultado:

1
2
3
4
5
Minimum by page count is Book{author='Nick Bostrom', name='Superintelligence', pageNumber=352}
Maximum by page count is Book{author='Ray Kurzweil', name='The Singularity is Near', pageNumber=652}

Minimum by author is Book{author='Max Tegmark', name='Our Mathematical Universe', pageNumber=432}
Maximum by author is Book{author='Ray Kurzweil', name='The Singularity is Near', pageNumber=652}

Usar un bucle for

Finalmente, el viejo bucle for se puede usar para buscar un elemento máximo o mínimo:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
List<Book> bookList = new ArrayList<>();
bookList.add(new Book("Nick Bostrom", "Superintelligence", 352));
bookList.add(new Book("Ray Kurzweil", "The Singularity is Near", 652));
bookList.add(new Book("Max Tegmark", "Our Mathematical Universe", 432));

List<Integer> intList = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9);

// Instantiate as the first book initially
Book maxBook = bookList.get(0);
Integer maxInt = 0;

for (Book book : bookList) {
    // book.getPageNumber() < minBook.getPageNumber()
    if (book.getPageNumber() > maxBook.getPageNumber()) {
        maxBook = book;
    }
}

for (Integer integer : intList) {
    // integer < minInt
    if (integer > maxInt) {
        maxInt = integer;
    }
}

System.out.println(String.format("Maximum by page count is %s", maxBook));
System.out.println(String.format("Maximum int is %s", maxInt));

Esto da como resultado los mismos resultados que hemos estado viendo hasta ahora:

1
2
Maximum by page count is Book{author='Ray Kurzweil', name='The Singularity is Near', pageNumber=652}
Maximum int is 9

Conclusión

En esta guía, hemos echado un vistazo a cómo encontrar los elementos máximos y mínimos de una colección de Java. Hemos echado un vistazo tanto a los tipos primitivos como a los objetos personalizados, los comparadores predeterminados y personalizados, y las mejores prácticas, incluido un bucle for manual.

Licensed under CC BY-NC-SA 4.0