Implementar la interfaz del Comparador
La interfaz Comparator se puede ver en todas partes de la biblioteca JDK, desde la búsqueda hasta la clasificación, las operaciones inversas, etc. En Java 8, se convierte en una interfaz funcional. La ventaja de esto es que podemos usar la sintaxis de transmisión para implementar el comparador.
Implementemos Comparator de varias formas diferentes para ver el valor de la nueva sintaxis. Tus dedos te lo agradecerán. No tener que implementar clases internas anónimas te ahorra muchas pulsaciones de teclas.
Ordenar usando Comparador
El siguiente ejemplo utilizará diferentes métodos de comparación para ordenar un grupo de personas. Primero creemos un Persona JavaBean.
Copie el código de código de la siguiente manera:
Persona de clase pública {
nombre de cadena final privado;
edad de entrada final privada;
Persona pública (cadena final el nombre, final int la edad) {
nombre = elNombre;
edad = laEdad;
}
cadena pública getName() { devolver nombre }
public int getAge() { edad de retorno }
public int ageDifference(final Persona otra) {
edad de retorno - otra.edad;
}
cadena pública toString() {
return String.format("%s - %d", nombre, edad);
}
}
Podemos implementar la interfaz Comparator a través de la clase Persona, pero de esta manera solo podemos usar un método de comparación. Queremos poder comparar diferentes atributos, como nombre, edad o una combinación de estos. Para realizar la comparación de manera flexible, podemos usar Comparator para generar código relevante cuando necesitemos comparar.
Primero creemos una lista de Personas, cada una con un nombre y edad diferentes.
Copie el código de código de la siguiente manera:
Lista final<Persona> personas = Arrays.asList(
nueva persona("Juan", 20),
nueva persona("Sara", 21),
nueva persona("Jane", 21),
nueva persona("Greg", 35));
Podemos ordenar a las personas en orden ascendente o descendente por su nombre o edad. El método general consiste en utilizar clases internas anónimas para implementar la interfaz Comparator. Si se escribe de esta manera, sólo el código más relevante tiene significado y el resto es sólo una formalidad. El uso de expresiones lambda puede centrarse en la esencia de la comparación.
Primero ordenémoslos por edad, de menor a mayor.
Ahora que tenemos un objeto Lista, podemos usar su método sort() para ordenar. Sin embargo, este método también tiene sus problemas. Este es un método nulo, lo que significa que cuando llamemos a este método, la lista cambiará. Para conservar la lista original, primero debemos hacer una copia y luego llamar al método sort(). Fue demasiado esfuerzo. En este momento tenemos que recurrir a la clase Stream en busca de ayuda.
Podemos obtener un objeto Stream de la Lista y luego llamar a su método ordenado(). Devuelve una colección ordenada en lugar de modificar la colección original. Con este método, puede configurar fácilmente los parámetros de Comparator.
Copie el código de código de la siguiente manera:
Lista<Persona>edadAscendente =
personas.stream()
.sorted((persona1, persona2) -> persona1.ageDifference(persona2))
.collect(toList());
printPeople("Ordenado en orden ascendente por edad: ", ascendingAge);
Primero convertimos la lista en un objeto Stream mediante el método stream(). Luego llame a su método sorted(). Este método acepta un parámetro Comparador. Dado que Comparator es una interfaz funcional, podemos pasar una expresión lambda. Finalmente llamamos al método de recopilación y le pedimos que almacene los resultados en una lista. El método de recopilación es un reductor que puede generar los objetos durante el proceso de iteración en un formato o tipo específico. El método toList() es un método estático de la clase Collectors.
El método abstracto compareTo() de Comparator recibe dos parámetros, que son los objetos a comparar, y devuelve un resultado de tipo int. Para ser compatible con esto, nuestra expresión lambda también recibe dos parámetros, dos objetos Persona, cuyos tipos son deducidos automáticamente por el compilador. Devolvemos un tipo int que indica si los objetos comparados son iguales.
Como queremos ordenar por edad, compararemos las edades de los dos objetos y luego devolveremos el resultado de la comparación. Si son del mismo tamaño, devuelve 0. De lo contrario, se devuelve un número negativo si la primera persona es más joven y un número positivo si la primera persona es mayor.
El método sorted() atravesará cada elemento de la colección de destino y llamará al Comparador especificado para determinar el orden de clasificación de los elementos. El método de ejecución del método sorted() es algo similar al método reduce() mencionado anteriormente. El método reduce() reduce gradualmente la lista a un resultado. El método sorted() ordena según los resultados de la comparación.
Una vez que hayamos ordenado, queremos imprimir los resultados, así que llamamos a un método printPeople(); implementemos este método.
Copie el código de código de la siguiente manera:
printPeople vacío estático público (
mensaje de cadena final, lista final<Persona> personas) {
System.out.println(mensaje);
personas.forEach(System.out::println);
}
En este método, primero imprimimos un mensaje, luego recorremos la lista e imprimimos cada elemento que contiene.
Llamemos al método sorted() para ver cómo ordenará las personas en la lista de menor a mayor.
Copie el código de código de la siguiente manera:
Ordenados en orden ascendente por edad:
Juan - 20
sara - 21
jane - 21
Greg - 35
Echemos otro vistazo al método sorted() para realizar una mejora.
Copie el código de código de la siguiente manera:
.sorted((persona1, persona2) -> persona1.ageDifference(persona2))
En la expresión lambda pasada, simplemente enrutamos estos dos parámetros: el primer parámetro se usa como destino de llamada del método ageDifference() y el segundo parámetro se usa como su parámetro. Pero no podemos escribirlo así, sino usar un modo de espacio de oficina, es decir, usar referencias de métodos y dejar que el compilador de Java haga el enrutamiento.
El enrutamiento de parámetros utilizado aquí es un poco diferente de lo que vimos antes. Vimos anteriormente que los argumentos se pasan como objetivos de llamada o como parámetros de llamada. Ahora tenemos dos parámetros y queremos dividirlos en dos partes, una como destino de la llamada al método y la segunda como parámetro. No se preocupe, el compilador de Java le dirá: "Yo me encargo de esto".
Podemos reemplazar la expresión lambda en el método sorted() anterior con un método ageDifference breve y conciso.
Copie el código de código de la siguiente manera:
personas.stream()
.ordenado(Persona::diferenciadeedad)
Este código es muy conciso gracias a las referencias de métodos proporcionadas por el compilador de Java. El compilador recibe dos parámetros de instancia de persona y utiliza el primero como destino del método ageDifference() y el segundo como parámetro del método. Dejamos que el compilador haga este trabajo en lugar de escribir el código directamente. Al utilizar este método, debemos asegurarnos de que el primer parámetro sea el destino de llamada del método al que se hace referencia y el restante sea el parámetro de entrada del método.
Comparador de reutilización
Es fácil ordenar las personas en la lista de menor a mayor, y también es fácil ordenar de mayor a menor. Probémoslo.
Copie el código de código de la siguiente manera:
printPeople("Ordenado en orden descendente por edad: ",
personas.stream()
.sorted((persona1, persona2) -> persona2.ageDifference(persona1))
.collect(toList()));
Llamamos al método sorted() y le pasamos una expresión lambda, que encaja en la interfaz Comparator, como en el ejemplo anterior. La única diferencia es la implementación de esta expresión lambda: hemos cambiado el orden de las personas a comparar. Los resultados deben ordenarse de mayor a menor según su edad. Echemos un vistazo.
Copie el código de código de la siguiente manera:
Ordenados en orden descendente por edad:
Greg - 35
sara - 21
jane - 21
Juan - 20
No hace falta mucho esfuerzo para cambiar simplemente la lógica de la comparación. Pero no podemos reconstruir esta versión en una referencia de método porque el orden de los parámetros no cumple con las reglas de enrutamiento de parámetros para las referencias de métodos; el primer parámetro no se utiliza como el destino de llamada del método, sino como un parámetro del método. Hay una manera de resolver este problema que también reduce la duplicación de esfuerzos. Veamos cómo hacerlo.
Hemos creado dos expresiones lambda antes: una es para ordenar por edad de pequeña a grande y la otra es para ordenar de grande a pequeña. Hacerlo provocará redundancia y duplicación de código y violará el principio DRY. Si solo queremos ajustar el orden de clasificación, JDK proporciona un método inverso, que tiene un modificador de método especial, predeterminado. Lo discutiremos en el método predeterminado en la página 77. Aquí primero usamos el método invertido() para eliminar la redundancia.
Copie el código de código de la siguiente manera:
Comparador<Persona> compararAscendente =
(persona1, persona2) -> persona1.ageDifference(persona2);
Comparador<Persona> compareDescending = compareAscending.reversed();
Primero creamos un Comparador, compareAscending, para ordenar a las personas por edad, de menor a mayor. Para invertir el orden de comparación, en lugar de escribir este código nuevamente, solo necesitamos llamar al método invertido () del primer Comparador para obtener el segundo objeto Comparador. Bajo el capó del método invertido(), crea un comparador para invertir el orden de los parámetros comparados. Esto muestra que invertido también es un método de orden superior: crea y devuelve una función sin efectos secundarios. Usamos estos dos comparadores en el código.
Copie el código de código de la siguiente manera:
printPeople("Ordenado en orden ascendente por edad: ",
personas.stream()
.ordenado(compararAscendente)
.collect(aLista())
);
printPeople("Ordenado en orden descendente por edad: ",
personas.stream()
.ordenado(compararDescendente)
.collect(aLista())
);
Se puede ver claramente en el código que estas nuevas características de Java8 han reducido en gran medida la redundancia y la complejidad del código, pero los beneficios son mucho más que estos. Hay infinitas posibilidades esperando que las explore en el JDK.
Ya podemos ordenar por edad y también es fácil ordenar por nombre. Ordenémoslos lexicográficamente por nombre. De manera similar, solo necesitamos cambiar la lógica en la expresión lambda.
Copie el código de código de la siguiente manera:
printPeople("Ordenado en orden ascendente por nombre: ",
personas.stream()
.sorted((persona1, persona2) ->
persona1.getName().compareTo(persona2.getName()))
.collect(toList()));
Los resultados de salida se ordenarán en orden lexicográfico por nombre.
Copie el código de código de la siguiente manera:
Ordenado en orden ascendente por nombre:
Greg - 35
jane - 21
Juan - 20
sara - 21
Hasta ahora, hemos ordenado por edad o por nombre. Podemos hacer que la lógica de las expresiones lambda sea más inteligente. Por ejemplo, podemos ordenar por edad y nombre al mismo tiempo.
Escojamos a la persona más joven de la lista. Primero podemos ordenar por edad de menor a mayor y luego seleccionar la primera en los resultados. Pero eso en realidad no funciona. Stream tiene un método min() para lograr esto. Este método también acepta un Comparador, pero devuelve el objeto más pequeño de la colección. Usémoslo.
Copie el código de código de la siguiente manera:
personas.stream()
.min(Persona::diferenciadeedad)
.ifPresent(el más joven -> System.out.println("El más joven: " + el más joven));
Al llamar al método min(), utilizamos la referencia del método ageDifference. El método min() devuelve un objeto Optinal porque la lista puede estar vacía y puede haber más de una persona más joven en ella. Luego obtenemos a la persona más joven a través del método ifPrsend() de Optinal e imprimimos su información detallada. Echemos un vistazo al resultado.
Copie el código de código de la siguiente manera:
Más joven: John - 20
Exportar el más antiguo también es muy sencillo. Simplemente pase la referencia de este método a un método max().
Copie el código de código de la siguiente manera:
personas.stream()
.max(Persona::diferenciadeedad)
.ifPresent(mayor -> System.out.println("Mayor: " + mayor));
Echemos un vistazo al nombre y la edad del mayor.
Copie el código de código de la siguiente manera:
Mayor: Greg - 35
Con expresiones lambda y referencias de métodos, la implementación de comparadores se vuelve más simple y conveniente. JDK también introduce muchos métodos convenientes para la clase Compararor, lo que nos permite comparar más fácilmente, como veremos a continuación.
Múltiples comparaciones y comparaciones de transmisión.
Echemos un vistazo a los nuevos métodos convenientes proporcionados por la interfaz Comparator y utilícelos para comparar múltiples propiedades.
Sigamos usando el ejemplo de la sección anterior. Ordenando por nombre, esto es lo que escribimos arriba:
Copie el código de código de la siguiente manera:
personas.stream()
.sorted((persona1, persona2) ->
persona1.getName().compareTo(persona2.getName()));
Comparado con el método de escritura de clase interna del siglo pasado, este método de escritura es simplemente demasiado simple. Sin embargo, se puede simplificar si usamos algunas funciones de la clase Comparador. El uso de estas funciones puede permitirnos expresar nuestro propósito de manera más fluida. Por ejemplo, si queremos ordenar por nombre, podemos escribir:
Copie el código de código de la siguiente manera:
Función final<Persona, Cadena> byName = persona -> persona.getName();
personas.stream()
.ordenado(comparando(porNombre));
En este código hemos importado el método estático comparando() de la clase Comparador. El método compare() utiliza la expresión lambda pasada para generar un objeto Comparador. En otras palabras, también es una función de orden superior que acepta una función como parámetro de entrada y devuelve otra función. Además de hacer que la sintaxis sea más concisa, dicho código también puede expresar mejor el problema real que queremos resolver.
Con él, las comparaciones múltiples pueden resultar más fluidas. Por ejemplo, el siguiente código que compara por nombre y edad lo dice todo:
Copie el código de código de la siguiente manera:
Función final<Persona, Entero> porEdad = persona -> persona.getAge();
Función final<Persona, Cadena> porSuNombre = persona -> persona.getName();
printPeople("Ordenado en orden ascendente por edad y nombre: ",
personas.stream()
.ordenado(comparando(porEdad).luegoComparando(porSuNombre))
.collect(toList()));
Primero creamos dos expresiones lambda, una devuelve la edad de la persona especificada y la otra devuelve su nombre. Al llamar al método sorted(), combinamos estas dos expresiones para que se puedan comparar múltiples atributos. El método compare() crea y devuelve un Comparador basado en la edad. Luego llamamos al método thenComparing() en el Comparador devuelto para crear un comparador combinado que compara la edad y el nombre. El resultado a continuación es el resultado de ordenar primero por edad y luego por nombre.
Copie el código de código de la siguiente manera:
Ordenado en orden ascendente por edad y nombre:
Juan - 20
jane - 21
Sara - 21
Greg - 35
Como puede ver, la implementación de Comparator se puede combinar fácilmente utilizando expresiones lambda y las nuevas clases de herramientas proporcionadas por el JDK. Presentamos a los coleccionistas a continuación.