Los perezosos no son los únicos que escriben sobre comparadores y comparaciones en Java. No soy perezoso, así que por favor ama y quéjate de otra explicación más. Espero que no sea superfluo. Y sí, este artículo es la respuesta a la pregunta: "¿ Puedes escribir un comparador de memoria? " Espero que todos puedan escribir un comparador de memoria después de leer este artículo.

Introducción
Como sabes, Java es un lenguaje orientado a objetos. Como resultado, es costumbre manipular objetos en Java. Pero tarde o temprano, te enfrentas a la tarea de comparar objetos en función de alguna característica. Por ejemplo : supongamos que tenemos algún mensaje descrito por laMessage
clase:
public static class Message {
private String message;
private int id;
public Message(String message) {
this.message = message;
this.id = new Random().nextInt(1000);
}
public String getMessage() {
return message;
}
public Integer getId() {
return id;
}
public String toString() {
return "[" + id + "] " + message;
}
}
Coloque esta clase en el compilador de Java de Tutorialspoint . No olvide agregar las declaraciones de importación también:
import java.util.Random;
import java.util.ArrayList;
import java.util.List;
En el main
método, crea varios mensajes:
public static void main(String[] args){
List<Message> messages = new ArrayList();
messages.add(new Message("Hello, World!"));
messages.add(new Message("Hello, Sun!"));
System.out.println(messages);
}
Pensemos en qué haríamos si quisiéramos compararlos. Por ejemplo, queremos ordenar por id. Y para crear un orden, necesitamos comparar de alguna manera los objetos para entender qué objeto debe ir primero (es decir, el más pequeño) y cuál debe seguir (es decir, el más grande). Comencemos con una clase como java.lang.Object . Sabemos que todas las clases heredan implícitamente la Object
clase. Y esto tiene sentido porque refleja el concepto de que "todo es un objeto" y proporciona un comportamiento común para todas las clases. Esta clase dicta que cada clase tiene dos métodos: → hashCode
El hashCode
método devuelve algo numérico (int
) representación del objeto. ¿Qué significa eso? Significa que si crea dos instancias diferentes de una clase, entonces deberían tener hashCode
s diferentes. La descripción del método dice tanto: "En la medida en que sea razonablemente práctico, el método hashCode definido por la clase Object devuelve enteros distintos para objetos distintos". En otras palabras, para dos instance
s diferentes, debe haber hashCode
s diferentes. Es decir, este método no es adecuado para nuestra comparación. → equals
. El equals
método responde a la pregunta "¿son estos objetos iguales?" y devuelve un boolean
." Por defecto, este método tiene el siguiente código:
public boolean equals(Object obj) {
return (this == obj);
}
Es decir, si este método no se anula, esencialmente dice si las referencias de objetos coinciden o no. Esto no es lo que queremos para nuestros mensajes, porque estamos interesados en los identificadores de mensajes, no en las referencias de objetos. E incluso si anulamos el equals
método, lo más que podemos esperar es saber si son iguales. Y esto no es suficiente para que determinemos el orden. Entonces, ¿qué necesitamos entonces? Necesitamos algo que se compare. El que compara es un Comparator
. Abra la API de Java y busque Comparator . De hecho, hay una java.util.Comparator
interfaz java.util.Comparator and java.util.Comparable
Como puede ver, tal interfaz existe. Una clase que lo implementa dice: "Implemento un método que compara objetos". Lo único que realmente necesita recordar es el contrato de comparación, que se expresa de la siguiente manera:
Comparator returns an int according to the following rules:
- It returns a negative int if the first object is smaller
- It returns a positive int if the first object is larger
- It returns zero if the objects are equal
Ahora vamos a escribir un comparador. Tendremos que importar java.util.Comparator
. Después de la declaración de importación, agregue lo siguiente al main
método: Comparator<Message> comparator = new Comparator<Message>();
Por supuesto, esto no funcionará, porque Comparator
es una interfaz. Entonces agregamos llaves {}
después de los paréntesis. Escribe el siguiente método dentro de las llaves:
public int compare(Message o1, Message o2) {
return o1.getId().compareTo(o2.getId());
}
Ni siquiera necesitas recordar la ortografía. Un comparador es aquel que realiza una comparación, es decir, compara. Para indicar el orden relativo de los objetos, devolvemos un int
. Eso es básicamente todo. Bonito y fácil. Como puede ver en el ejemplo, además de Comparator, hay otra interfaz, java.lang.Comparable
que requiere que implementemos el compareTo
método. Esta interfaz dice: "una clase que me implementa hace posible comparar instancias de la clase". Por ejemplo, Integer
la implementación de compare
To es la siguiente:
(x < y) ? -1 : ((x == y) ? 0 : 1)
Java 8 introdujo algunos cambios agradables. Si echa un vistazo más de cerca a la Comparator
interfaz, verá la @FunctionalInterface
anotación encima de ella. Esta anotación es para fines informativos y nos dice que esta interfaz es funcional. Esto significa que esta interfaz tiene solo 1 método abstracto, que es un método sin implementación. ¿Qué nos da esto? Ahora podemos escribir el código del comparador así:
Comparator<Message> comparator = (o1, o2) -> o1.getId().compareTo(o2.getId());
Nombramos las variables entre paréntesis. Java verá que debido a que solo hay un método, entonces el número requerido y los tipos de parámetros de entrada son claros. Luego usamos el operador de flecha para pasarlos a esta parte del código. Además, gracias a Java 8, ahora tenemos métodos predeterminados en las interfaces. Estos métodos aparecen por defecto cuando implementamos una interfaz. La Comparator
interfaz tiene varios. Por ejemplo:
Comparator moreImportant = Comparator.reverseOrder();
Comparator lessImportant = Comparator.naturalOrder();
Hay otro método que hará que su código sea más limpio. Eche un vistazo al ejemplo anterior, donde definimos nuestro comparador. ¿Qué hace? Es bastante primitivo. Simplemente toma un objeto y extrae algún valor que sea "comparable". Por ejemplo, Integer
implements comparable
, por lo que podemos realizar una operación compareTo en los valores de los campos de identificación del mensaje. Esta función de comparación simple se puede escribir así:
Comparator<Message> comparator = Comparator.comparing(obj -> obj.getId());
En otras palabras, tenemos un Comparator
que compara así: toma objetos, usa el getId()
método para obtener un Comparable
de ellos y luego los usa compareTo
para comparar. Y no hay construcciones más horribles. Y finalmente, quiero señalar una característica más. Los comparadores se pueden encadenar. Por ejemplo:
Comparator<Message> comparator = Comparator.comparing(obj -> obj.getId());
comparator = comparator.thenComparing(obj -> obj.getMessage().length());
Solicitud
Declarar un comparador resulta bastante lógico, ¿no crees? Ahora tenemos que ver cómo y dónde usarlo. →Collections.sort(java.util.Collections)
Podemos, por supuesto, ordenar las colecciones de esta manera. Pero no todas las colecciones, solo listas. No hay nada inusual aquí, porque las listas son el tipo de colecciones en las que accedes a los elementos por su índice. Esto permite que el segundo elemento se intercambie con el tercer elemento. Es por eso que el siguiente método de clasificación es solo para listas:
Comparator<Message> comparator = Comparator.comparing(obj -> obj.getId());
Collections.sort(messages, comparator);
→ Arrays.sort(java.util.Arrays)
Las matrices también son fáciles de ordenar. Nuevamente, por la misma razón: se accede a sus elementos mediante index. → Descendants of java.util.SortedSet and java.util.SortedMap
Lo recordará Set
y Map
no garantiza el orden en que se almacenan los elementos. PERO, tenemos implementaciones especiales que garantizan el orden. Y si los elementos de una colección no se implementan java.util.Comparable
, podemos pasar Comparator
a su constructor:
Set<Message> msgSet = new TreeSet(comparator);
→ Stream API
En Stream API, que apareció en Java 8, los comparadores le permiten simplificar el trabajo con elementos de flujo. Por ejemplo, supongamos que necesitamos una secuencia de números aleatorios del 0 al 999, ambos inclusive:
Supplier<Integer> randomizer = () -> new Random().nextInt(1000);
Stream.generate(randomizer)
.limit(10)
.sorted(Comparator.naturalOrder())
.forEach(e -> System.out.println(e));
Podríamos detenernos aquí, pero hay problemas aún más interesantes. Por ejemplo, suponga que necesita preparar un correo electrónico Map
, donde la clave es una identificación de mensaje. Además, queremos ordenar estas claves, por lo que comenzaremos con el siguiente código:
Map<Integer, Message> collected = Arrays.stream(messages)
.sorted(Comparator.comparing(msg -> msg.getId()))
.collect(Collectors.toMap(msg -> msg.getId(), msg -> msg));
De hecho, tenemos un HashMap
aquí. Y como sabemos, no garantiza ningún pedido. Como resultado, nuestros elementos, que fueron ordenados por id, simplemente pierden su orden. No es bueno. Tendremos que cambiar un poco nuestro colector:
Map<Integer, Message> collected = Arrays.stream(messages)
.sorted(Comparator.comparing(msg -> msg.getId()))
.collect(Collectors.toMap(msg -> msg.getId(), msg -> msg, (oldValue, newValue) -> oldValue, TreeMap::new));
El código ha comenzado a parecer un poco más aterrador, pero ahora el problema se resuelve correctamente. Lea más sobre las diversas agrupaciones aquí:
Puedes crear tu propio colector. Lea más aquí: "Creación de un recopilador personalizado en Java 8" . Y se beneficiará de leer la discusión aquí: "Lista de Java 8 para mapear con flujo" .
Trampa de caída
Comparator
y Comparable
son buenos Pero hay un matiz que debes recordar. Cuando una clase realiza la clasificación, espera que su clase se pueda convertir a un archivo Comparable
. Si este no es el caso, recibirá un error en tiempo de ejecución. Veamos un ejemplo:
SortedSet<Message> msg = new TreeSet<>();
msg.add(new Message(2, "Developer".getBytes()));
Parece que nada está mal aquí. Pero, de hecho, en nuestro ejemplo, fallará con un error: java.lang.ClassCastException: Message cannot be cast to java.lang.Comparable
Y todo porque intentó ordenar los elementos ( SortedSet
después de todo, es un )... pero no pudo. No olvide esto cuando trabaje con SortedMap
y SortedSet
.
Más lectura: |
---|
GO TO FULL VERSION