Cómo ordenar arrays y colecciones de objetos en Java

Escrito por el .
java planeta-codigo
Enlace permanente Comentarios

Al implementar un algoritmo es común querer iterar los elementos de una colección en un orden según un criterio, por ejemplo, si se trata de números de menor a mayor, si se trata de fechas de menor a mayor y si se trata de personas por orden alfabético del nombre, de menor a mayor edad o de menor a mayor antigüedad en la empresa, también es posible la necesidad de iterar en orden inverso. El JDK de Java proporciona interfaces para implementar la ordenación de objetos y que ya implementan algunos de los algoritmos de ordenación conocidos.

Java

Entre las clases proporcionadas en el JDK de Java se proporciona un amplio conjunto de clases dedicadas a colecciones que son el fundamento de muchos algoritmos de programación y programas. Las clases de colecciones sirven para almacenar referencias a objetos, algunas colecciones no tiene un orden definido como Set y Map y otras definen un orden en la iteración de la colección como List pero no un orden entre los elementos, otras colecciones como TreeSet y TreeMap guardan los elementos ordenados según un criterio manteniéndose ordenada incluyendo al realizar inserciones y eliminaciones de elementos.

Hay varios algoritmos de ordenación conocidos como la ordenación por burbuja o bubble sort, por inserción, merge sort o quicksort cada uno con diferentes propiedades de complejidad o consumo de memoria. Normalmente no es necesario implementar estos algoritmos, sino que ya están implementados en las bibliotecas y en el caso de Java en las clases del JDK.

El usar colecciones ordenadas por un orden es una funcionalidad común al implementar programas lo único que es necesario es utilizar la colección adecuada y únicamente crear una clase que implemente la interfaz Comparator que determina el orden entre dos elementos, aplicando la comparación a los elementos de la colección con el algoritmo de ordenación ser ordena la colección.

Algoritmo de ordenación bubble-sort Algoritmo de ordenación merge-sort

Algoritmo de ordenación bubble-sort y merge-sort

La interfaz Comparator

La interfaz Comprator es una interfaz funcional, por tener un único método a implementar, que recibe dos argumentos y devuelve un entero. Los argumentos son los dos objetos a comparar y el resultado indica cual es el orden de los elementos entre sí.

Si el resultado es un -1 se indica que el argumento a tiene un orden menor que b, si devuelve un 0 el orden de los elementos es el mismo y si devuelve un 1 el argumento a tiene un orden superior a b.

Estas son implementaciones de Comparator utilizando referencias de métodos.

1
2
Comparator<Person> ageComparator = Comparator.comparing(Person::getAge);
Comparator<Person> hireComparator = Comparator.comparing(Person::getHired);
Comparators.java

Para ordenar cadenas alfabéticamente también es necesario crear un comparador, sin embargo, la ordenación de cadenas alfabéticamente no es tan simple como utilizar el método comprateTo de la clase String. Para ordenar cadenas alfabéticamente en Java hay de tener en cuenta letras con tildes, mayúsculas y minúsculas que varían según el idioma de las palabras, el método comprteTo que podría usarse para crear un Comprator no es válido y puede producir resultados inesperados ya que el String.compareTo ordena según el código de los caracteres sin tener en cuenta tildes ni mayúsculas ni minúsculas.

Esta es la implementación de un Comparator que ordena cadenas en orden ascendente de forma alfabética utilizando la clase Collator.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
private static class NameComparator implements Comparator<Person> {

    private Collator collator;

    public NameComparator() {
        this.collator = Collator.getInstance(new Locale("es"));
        collator.setStrength(Collator.TERTIARY);
    }

    @Override
    public int compare(Person o1, Person o2) {
        return collator.compare(o1.getName(), o2.getName());
    }
}
NameComparator.java

Con la clase Comparator es posible ordenar cualquier clase, en este ejemplo de clase Person se ordenan los objetos según su edad, fecha de contratación y nombre. Como en este caso es posible tener varias implementaciones de Comprator para una misma clase para ordenar los objetos por diferentes criterios.

La interfaz Comparable

Otra interfaz relacionada con la ordenación es la interfaz Comparable, es una interfaz que pueden implementar los objetos, la ordenación que se establece en la ordenación se le denomina el orden natural.

A diferencia de la clase Comparator de la que es posible crear varias implementaciones, las clases sólo pueden implementar una vez la interfaz Comparable.

 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
private static class Person implements Comparable<Person> {

    private String name;
    private int age;
    private LocalDate hired;

    public Person(String name, int age, LocalDate hired) {
        this.name = name;
        this.age = age;
        this.hired = hired;
    }

    ...

    @Override
    public int compareTo(Person o) {
        if (age < o.getAge()) {
            return -1;
        } else if (age > o.getAge()) {
            return 1;
        } else {
            return 0;
        }
    } 
}
Person-comparable.java

Cómo ordenar los elementos un array

La clase Arrays contiene varios métodos de utilidad entre ellos varios dedicados a la ordenación de los elementos de un array tanto para elementos primitivos como para objetos. Hay métodos que utilizan el la ordenación natural de la interfaz Comparable y hay métodos en los que es posible indicar la clase Comparator con el orden deseado entre los elementos.

 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
package io.github.picodotdev.blogbitix.javasort;

...

public class Main {

    public static void main(String[] args) {
        int[] array = new int[] { 64, 47, 33, 82, 91, 1, 45 };
        List<Integer> list = Arrays.stream(array).boxed().collect(Collectors.toList());
        List<Person> persons = List.of(
                new Person("Juan", 56, LocalDate.of(1982, 3, 26)),
                new Person("María", 24, LocalDate.of(2018, 8, 7)),
                new Person("Marisa", 63, LocalDate.of(2021, 4, 17)),
                new Person("Antonio", 41, LocalDate.of(2020, 5, 2))
        );

        Comparator<Person> nameComparator = new NameComparator();
        Comparator<Person> ageComparator = Comparator.comparing(Person::getAge);
        Comparator<Person> hireComparator = Comparator.comparing(Person::getHired);

        ...
    }

    ...
}
Main.java
1
2
3
4
5
System.out.println();
System.out.println("Array (sorted)");
int[] arraySorted = Arrays.copyOf(array, array.length);
Arrays.sort(arraySorted);
Arrays.stream(arraySorted).forEach(i -> System.out.println(i));
Array-sort.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
Array (unsorted)
64
47
33
82
91
1
45

...

Array (sorted)
1
33
45
47
64
82
91

...
System.out-array

Cómo ordenar los elementos de una colección

Las clase Collections es el equivalente de la clase Arrays para las colecciones, también tiene métodos de utilidad en este caso para las colecciones. Tiene un método sort para ordenar los elementos de una lista según el orden natural y para ordenar los elementos de la lista según el criterio de un Comparator.

A tener en cuenta que tanto los métodos sort de Arrays como de Collections no devuelven una nueva instancia de array o colección ordenada sino que modifican la instancia de array o colección que se proporciona para ordenar.

1
2
3
4
5
System.out.println();
System.out.println("List (sorted)");
List<Integer> listSorted = new ArrayList<>(list);
Collections.sort(listSorted);
listSorted.stream().forEach(i -> System.out.println(i));
List-sort.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
System.out.println();
System.out.println("Persons (sorted, natural)");
List<Person> personsSortedNatural = new ArrayList<>(persons);
Collections.sort(personsSortedNatural);
personsSortedNatural.stream().forEach(i -> System.out.println(i));

System.out.println();
System.out.println("Persons (sorted, name)");
List<Person> personsSortedName = new ArrayList<>(persons);
Collections.sort(personsSortedName, nameComparator);
personsSortedName.stream().forEach(i -> System.out.println(i));

System.out.println();
System.out.println("Persons (sorted, age)");
List<Person> personsSortedAge = new ArrayList<>(persons);
Collections.sort(personsSortedAge, ageComparator);
personsSortedAge.stream().forEach(i -> System.out.println(i));

System.out.println();
System.out.println("Persons (sorted, hired)");
List<Person> personsSortedHired = new ArrayList<>(persons);
Collections.sort(personsSortedHired, hireComparator);
personsSortedHired.stream().forEach(i -> System.out.println(i));
Person-sort.java
 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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
List (unsorted)
64
47
33
82
91
1
45

Persons (unsorted)
Person(name=Juan, age=56, hired=1982-03-26)
Person(name=María, age=24, hired=2018-08-07)
Person(name=Marisa, age=63, hired=2021-04-17)
Person(name=Antonio, age=41, hired=2020-05-02)

...

List (sorted)
1
33
45
47
64
82
91

Persons (sorted, natural)
Person(name=María, age=24, hired=2018-08-07)
Person(name=Antonio, age=41, hired=2020-05-02)
Person(name=Juan, age=56, hired=1982-03-26)
Person(name=Marisa, age=63, hired=2021-04-17)

Persons (sorted, name)
Person(name=Antonio, age=41, hired=2020-05-02)
Person(name=Juan, age=56, hired=1982-03-26)
Person(name=María, age=24, hired=2018-08-07)
Person(name=Marisa, age=63, hired=2021-04-17)

Persons (sorted, age)
Person(name=María, age=24, hired=2018-08-07)
Person(name=Antonio, age=41, hired=2020-05-02)
Person(name=Juan, age=56, hired=1982-03-26)
Person(name=Marisa, age=63, hired=2021-04-17)

Persons (sorted, hired)
Person(name=Juan, age=56, hired=1982-03-26)
Person(name=María, age=24, hired=2018-08-07)
Person(name=Antonio, age=41, hired=2020-05-02)
Person(name=Marisa, age=63, hired=2021-04-17)
System.out-collection

Invertir el orden

La interfaz Comprable establece un orden ya sea ascendente o descendente según el criterio que implementa, si en un caso se desea el orden inverso del comprador la propia interfaz Comparator permite obtener un Comparator con el orden inverso al de la instancia. También es posible obtener un comprador que ordene las referencias nulas al principio si es que hay alguna en la colección.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
System.out.println();
System.out.println("Array (sorted descending)");
Integer[] arraySorted = Arrays.stream(array).boxed().toArray(Integer[]::new);
Arrays.sort(arraySorted, Collections.reverseOrder());
Arrays.stream(arraySorted).forEach(i -> System.out.println(i));

System.out.println();
System.out.println("List (sorted descending)");
List<Integer> listSortedNatural = new ArrayList<>(list);
Collections.sort(listSortedNatural, Comparator.<Integer>naturalOrder().reversed());
listSortedNatural.stream().forEach(i -> System.out.println(i));

System.out.println();
System.out.println("Persons (sorted descending, age)");
List<Person> personsSortedAge = new ArrayList<>(persons);
Collections.sort(personsSortedAge, ageComparator.reversed());
personsSortedAge.stream().forEach(i -> System.out.println(i));
Sort-reverse.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Array (sorted descending)
91
82
64
47
45
33
1

List (sorted descending)
91
82
64
47
45
33
1

Persons (sorted descending, age)
Person(name=Marisa, age=63, hired=2021-04-17)
Person(name=Juan, age=56, hired=1982-03-26)
Person(name=Antonio, age=41, hired=2020-05-02)
Person(name=María, age=24, hired=2018-08-07)
System.out-reverse
Terminal

El código fuente completo del ejemplo puedes descargarlo del repositorio de ejemplos de Blog Bitix alojado en GitHub y probarlo en tu equipo ejecutando siguiente comando:
./gradlew run


Comparte el artículo: