1. Información general
Java 8 introdujo varias mejoras en la interfaz del Comparator , incluyendo un puñado de funciones estáticas que son de gran utilidad al crear un orden de clasificación para colecciones.
Las lambdas de Java 8 también se pueden aprovechar de manera efectiva con la interfaz Comparator . Se puede encontrar una explicación detallada de lambdas y Comparator aquí, y una crónica sobre clasificación y aplicaciones de Comparator se puede encontrar aquí.
En este tutorial, exploraremos varias funciones introducidas para la interfaz Comparator en Java 8 .
2. Comenzando
2.1. Ejemplo de clase de frijol
Para los ejemplos de este artículo, creemos un bean Empleado y usemos sus campos para comparar y ordenar:
public class Employee { String name; int age; double salary; long mobile; // constructors, getters & setters }
2.2. Nuestros datos de prueba
También creemos una matriz de empleados que se utilizarán para almacenar los resultados de nuestro tipo en varios casos de prueba a lo largo del artículo:
employees = new Employee[] { ... };
El orden inicial de elementos de empleados será:
[Employee(name=John, age=25, salary=3000.0, mobile=9922001), Employee(name=Ace, age=22, salary=2000.0, mobile=5924001), Employee(name=Keith, age=35, salary=4000.0, mobile=3924401)]
A lo largo del artículo, ordenaremos la matriz de empleados anterior usando diferentes funciones.
Para las afirmaciones de prueba, usaremos un conjunto de arreglos preordenados que compararemos con nuestros resultados de clasificación (es decir, el arreglo de empleados ) para diferentes escenarios.
Declaremos algunas de estas matrices:
@Before public void initData() { sortedEmployeesByName = new Employee[] {...}; sortedEmployeesByNameDesc = new Employee[] {...}; sortedEmployeesByAge = new Employee[] {...}; // ... }
Como siempre, no dude en consultar nuestro enlace de GitHub para obtener el código completo.
3. Uso de Comparator.comparing
Esta sección cubre las variantes del Comparator.comparing función estática.
3.1. Variante del selector de teclas
La función estática Comparator.comparing acepta una función de clave de clasificación y devuelve un Comparador para el tipo que contiene la clave de clasificación:
static
Comparator comparing( Function keyExtractor)
Para ver esto en acción, usemos el campo de nombre en Empleado como clave de clasificación y pasemos su referencia de método como un argumento de tipo Función. El Comparador devuelto por el mismo se usa para ordenar:
@Test public void whenComparing_thenSortedByName() { Comparator employeeNameComparator = Comparator.comparing(Employee::getName); Arrays.sort(employees, employeeNameComparator); assertTrue(Arrays.equals(employees, sortedEmployeesByName)); }
Como puede ver, los valores de la matriz de empleados se ordenan por nombre como resultado de la clasificación:
[Employee(name=Ace, age=22, salary=2000.0, mobile=5924001), Employee(name=John, age=25, salary=3000.0, mobile=9922001), Employee(name=Keith, age=35, salary=4000.0, mobile=3924401)]
3.2. Variante de selector de teclas y comparador
Hay otra opción que facilita anular el orden natural de la clave de clasificación al proporcionar el Comparador que crea un orden personalizado para la clave de clasificación:
static Comparator comparing( Function keyExtractor, Comparator keyComparator)
Modifiquemos la prueba anterior, anulando el orden natural de clasificación por el campo de nombre proporcionando un Comparador para ordenar los nombres en orden descendente como segundo argumento para Comparator.comparing :
@Test public void whenComparingWithComparator_thenSortedByNameDesc() { Comparator employeeNameComparator = Comparator.comparing( Employee::getName, (s1, s2) -> { return s2.compareTo(s1); }); Arrays.sort(employees, employeeNameComparator); assertTrue(Arrays.equals(employees, sortedEmployeesByNameDesc)); }
Como puede ver, los resultados se ordenan en orden descendente por nombre :
[Employee(name=Keith, age=35, salary=4000.0, mobile=3924401), Employee(name=John, age=25, salary=3000.0, mobile=9922001), Employee(name=Ace, age=22, salary=2000.0, mobile=5924001)]
3.3. Utilizando Comparator.reversed
Cuando se invoca en un Comparator existente , el método de instancia Comparator.reversed devuelve un nuevo Comparator que invierte el orden de clasificación del original.
Usemos el Comparador que clasifica a los empleados por nombre y lo invirtamos para que los empleados se clasifiquen en orden descendente del nombre :
@Test public void whenReversed_thenSortedByNameDesc() { Comparator employeeNameComparator = Comparator.comparing(Employee::getName); Comparator employeeNameComparatorReversed = employeeNameComparator.reversed(); Arrays.sort(employees, employeeNameComparatorReversed); assertTrue(Arrays.equals(employees, sortedEmployeesByNameDesc)); }
Los resultados se ordenan en orden descendente por nombre :
[Employee(name=Keith, age=35, salary=4000.0, mobile=3924401), Employee(name=John, age=25, salary=3000.0, mobile=9922001), Employee(name=Ace, age=22, salary=2000.0, mobile=5924001)]
3.4. Uso de Comparator.comparingInt
También hay una función Comparator.comparingInt que hace lo mismo que Comparator.comparing , pero solo toma selectores int . Probemos esto con un ejemplo en el que ordenamos a los empleados por edad :
@Test public void whenComparingInt_thenSortedByAge() { Comparator employeeAgeComparator = Comparator.comparingInt(Employee::getAge); Arrays.sort(employees, employeeAgeComparator); assertTrue(Arrays.equals(employees, sortedEmployeesByAge)); }
Veamos cómo se ordenan los valores de la matriz de empleados después de la clasificación:
[Employee(name=Ace, age=22, salary=2000.0, mobile=5924001), Employee(name=John, age=25, salary=3000.0, mobile=9922001), Employee(name=Keith, age=35, salary=4000.0, mobile=3924401)]
3.5. Uso de Comparator.comparingLong
De manera similar a lo que hicimos con las claves int , veamos un ejemplo usando Comparator.comparingLong para considerar una clave de clasificación de tipo long ordenando la matriz de empleados por el campo móvil :
@Test public void whenComparingLong_thenSortedByMobile() { Comparator employeeMobileComparator = Comparator.comparingLong(Employee::getMobile); Arrays.sort(employees, employeeMobileComparator); assertTrue(Arrays.equals(employees, sortedEmployeesByMobile)); }
Let's see how the employees array values are ordered after the sort with mobile as the key:
[Employee(name=Keith, age=35, salary=4000.0, mobile=3924401), Employee(name=Ace, age=22, salary=2000.0, mobile=5924001), Employee(name=John, age=25, salary=3000.0, mobile=9922001)]
3.6. Using Comparator.comparingDouble
Again, similar to what we did for int and long keys, let's see an example using Comparator.comparingDouble to consider a sort key of type double by ordering the employees array by the salary field:
@Test public void whenComparingDouble_thenSortedBySalary() { Comparator employeeSalaryComparator = Comparator.comparingDouble(Employee::getSalary); Arrays.sort(employees, employeeSalaryComparator); assertTrue(Arrays.equals(employees, sortedEmployeesBySalary)); }
Let's see how the employees array values are ordered after the sort with salary as the sort key:
[Employee(name=Ace, age=22, salary=2000.0, mobile=5924001), Employee(name=John, age=25, salary=3000.0, mobile=9922001), Employee(name=Keith, age=35, salary=4000.0, mobile=3924401)]
4. Considering Natural Order in Comparator
The natural order is defined by the behavior of the Comparable interface implementation. More information about the difference between Comparator and uses of the Comparable interface can be found in this article.
Let's implement Comparable in our Employee class so that we can try the naturalOrder and reverseOrder functions of the Comparator interface:
public class Employee implements Comparable{ // ... @Override public int compareTo(Employee argEmployee) { return name.compareTo(argEmployee.getName()); } }
4.1. Using Natural Order
The naturalOrder function returns the Comparator for the return type mentioned in the signature:
static
Comparator naturalOrder()
Given the above logic to compare employees based on name field, let's use this function to obtain to a Comparator which sorts the employees array in natural order:
@Test public void whenNaturalOrder_thenSortedByName() { Comparator employeeNameComparator = Comparator. naturalOrder(); Arrays.sort(employees, employeeNameComparator); assertTrue(Arrays.equals(employees, sortedEmployeesByName)); }
Let's see how the employees array values are ordered after the sort:
[Employee(name=Ace, age=22, salary=2000.0, mobile=5924001), Employee(name=John, age=25, salary=3000.0, mobile=9922001), Employee(name=Keith, age=35, salary=4000.0, mobile=3924401)]
4.2. Using Reverse Natural Order
Similar to naturalOrder, let's use the reverseOrder method to generate a Comparator which will produce a reverse ordering of employees to the one in the naturalOrder example:
@Test public void whenReverseOrder_thenSortedByNameDesc() { Comparator employeeNameComparator = Comparator. reverseOrder(); Arrays.sort(employees, employeeNameComparator); assertTrue(Arrays.equals(employees, sortedEmployeesByNameDesc)); }
Let's see how the employees array values are ordered after the sort:
[Employee(name=Keith, age=35, salary=4000.0, mobile=3924401), Employee(name=John, age=25, salary=3000.0, mobile=9922001), Employee(name=Ace, age=22, salary=2000.0, mobile=5924001)]
5. Considering Null Values in Comparator
This section covers functions nullsFirst and nullsLast, which consider null values in ordering and keep the null values at the beginning or end of the ordering sequence.
5.1. Considering Null First
Let's randomly insert null values in employees array:
[Employee(name=John, age=25, salary=3000.0, mobile=9922001), null, Employee(name=Ace, age=22, salary=2000.0, mobile=5924001), null, Employee(name=Keith, age=35, salary=4000.0, mobile=3924401)]
The nullsFirst function will return a Comparator that keeps all nulls at the beginning of the ordering sequence:
@Test public void whenNullsFirst_thenSortedByNameWithNullsFirst() { Comparator employeeNameComparator = Comparator.comparing(Employee::getName); Comparator employeeNameComparator_nullFirst = Comparator.nullsFirst(employeeNameComparator); Arrays.sort(employeesArrayWithNulls, employeeNameComparator_nullFirst); assertTrue(Arrays.equals( employeesArrayWithNulls, sortedEmployeesArray_WithNullsFirst)); }
Let's see how the employees array values are ordered after the sort:
[null, null, Employee(name=Ace, age=22, salary=2000.0, mobile=5924001), Employee(name=John, age=25, salary=3000.0, mobile=9922001), Employee(name=Keith, age=35, salary=4000.0, mobile=3924401)]
5.2. Considering Null Last
The nullsLast function will return a Comparator that keeps all nulls at the end of the ordering sequence:
@Test public void whenNullsLast_thenSortedByNameWithNullsLast() { Comparator employeeNameComparator = Comparator.comparing(Employee::getName); Comparator employeeNameComparator_nullLast = Comparator.nullsLast(employeeNameComparator); Arrays.sort(employeesArrayWithNulls, employeeNameComparator_nullLast); assertTrue(Arrays.equals( employeesArrayWithNulls, sortedEmployeesArray_WithNullsLast)); }
Let's see how the employees array values are ordered after the sort:
[Employee(name=Ace, age=22, salary=2000.0, mobile=5924001), Employee(name=John, age=25, salary=3000.0, mobile=9922001), Employee(name=Keith, age=35, salary=4000.0, mobile=3924401), null, null]
6. Using Comparator.thenComparing
The thenComparing function lets you set up lexicographical ordering of values by provisioning multiple sort keys in a particular sequence.
Let's consider another array of Employee class:
someMoreEmployees = new Employee[] { ... };
Consider the following sequence of elements in the above array:
[Employee(name=Jake, age=25, salary=3000.0, mobile=9922001), Employee(name=Jake, age=22, salary=2000.0, mobile=5924001), Employee(name=Ace, age=22, salary=3000.0, mobile=6423001), Employee(name=Keith, age=35, salary=4000.0, mobile=3924401)]
Let's write a sequence of comparisons as age followed by the name and see the ordering of this array:
@Test public void whenThenComparing_thenSortedByAgeName(){ Comparator employee_Age_Name_Comparator = Comparator.comparing(Employee::getAge) .thenComparing(Employee::getName); Arrays.sort(someMoreEmployees, employee_Age_Name_Comparator); assertTrue(Arrays.equals(someMoreEmployees, sortedEmployeesByAgeName)); }
Here the ordering will be done by age, and for the values with the same age, ordering will be done by name. Let's observe this in the sequence we receive after sorting:
[Employee(name=Ace, age=22, salary=3000.0, mobile=6423001), Employee(name=Jake, age=22, salary=2000.0, mobile=5924001), Employee(name=Jake, age=25, salary=3000.0, mobile=9922001), Employee(name=Keith, age=35, salary=4000.0, mobile=3924401)]
Let's use the other version of thenComparing that is thenComparingInt, by changing the lexicographical sequence to name followed by age:
@Test public void whenThenComparing_thenSortedByNameAge() { Comparator employee_Name_Age_Comparator = Comparator.comparing(Employee::getName) .thenComparingInt(Employee::getAge); Arrays.sort(someMoreEmployees, employee_Name_Age_Comparator); assertTrue(Arrays.equals(someMoreEmployees, sortedEmployeesByNameAge)); }
Let's see how the employees array values are ordered after the sort:
[Employee(name=Ace, age=22, salary=3000.0, mobile=6423001), Employee(name=Jake, age=22, salary=2000.0, mobile=5924001), Employee(name=Jake, age=25, salary=3000.0, mobile=9922001), Employee(name=Keith, age=35, salary=4000.0, mobile=3924401)]
Similarly, there are functions thenComparingLong and thenComparingDouble for using long and double sorting keys.
7. Conclusion
Este artículo es una guía de varias características introducidas en Java 8 para la interfaz Comparator .
Como de costumbre, el código fuente se puede encontrar en Github.