JavaRush /Blog Java /Random-ES /Comparador en Java
Viacheslav
Nivel 3

Comparador en Java

Publicado en el grupo Random-ES
Sólo los perezosos no han escrito sobre Comparator y la comparación en Java. No soy perezoso, así que les pido que amen y favorezcan una variació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 después de leer este artículo todos puedan escribir un comparador de memoria.
Comparador en Java - 1
Introducción Se sabe que Java es un lenguaje orientado a objetos. Como resultado, en Java es común operar con objetos. Pero tarde o temprano surge la tarea de comparar objetos según algún principio. Entonces, dado: Tenemos algún mensaje, que se describe mediante la clase Mensaje:
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;
    }
}
Agreguemos esta clase al compilador Java de Tutorialspoint . Recordemos también agregar importaciones:
import java.util.Random;
import java.util.ArrayList;
import java.util.List;
En el método principal crearemos 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é debemos hacer si queremos compararlos? Por ejemplo, queremos ordenar por identificación. Y para crear orden, es necesario comparar objetos de alguna manera para comprender qué objeto es el anterior (es decir, más pequeño) y cuál es el siguiente (es decir, más grande). Comencemos con una clase como java.lang.Object . Como sabemos, todas las clases heredan implícitamente de esta clase de Objeto. Y esto es lógico, porque Básicamente, esto expresa el concepto: "Todo es un objeto" y proporciona un comportamiento común para todas las clases. Y esta clase define que cada clase tiene dos métodos: → hashCode El método hashCode devuelve alguna representación numérica (int) del objeto como una instancia de la clase. ¿Qué significa? Esto significa que si creó dos instancias diferentes de una clase, dado que las instancias son diferentes, su código hash debería ser diferente. Esto es lo que dice en la descripción del método: "Por mucho que sea razonablemente práctico, el método hashCode definido por la clase Object devuelve enteros distintos para objetos distintos". Es decir, si se trata de dos instancias diferentes, entonces deberían tener diferentes códigos hash. Es decir, este método no es adecuado para nuestra comparación. → igual El método igual responde a la pregunta "¿son los objetos iguales" y devuelve un valor booleano. Este método tiene el código predeterminado:
public boolean equals(Object obj) {
    return (this == obj);
}
Es decir, sin anular este método en un objeto, este método esencialmente dice si las referencias al objeto coinciden o no. Esto no es adecuado para nuestros mensajes, porque no nos interesan los enlaces al objeto, sino la identificación del mensaje. E incluso si anulamos el método de iguales, el máximo que obtendríamos es: "Son iguales" o "No son iguales". Pero esto no nos basta para determinar el orden.

Comparador y comparable en Java

¿Qué nos conviene? Si traducimos la palabra "comparar" al inglés en el traductor, obtendremos la traducción "comparar". Genial, entonces necesitamos a alguien que pueda comparar. Si comparas esta comparación, entonces quien compara es el Comparador. Abramos Java Api y busquemos Comparator allí . Y, de hecho, existe tal interfaz: java.util.Comparator java.util.Comparator y java.lang.Comparable Como puede ver, existe tal interfaz. La clase que lo implementa dice que "estoy implementando una función para comparar objetos". Lo único que realmente hay que recordar es el contrato comparador, que se expresa de la siguiente manera:

Comparator возвращает int по следующей схеме: 
  • отрицательный int (первый un objeto отрицательный, то есть меньше)
  • положительный int (первый un objeto положительный, хороший, то есть больший)
  • ноль = un objetoы равны
Ahora escribamos un comparador. Necesitaremos importar java.util.Comparator . Después de la importación, agregue un método a main: Comparator<Message> comparator = new Comparator<Message>(); Naturalmente, esto no funcionará, porque Comparador es una interfaz. Por tanto, tras los paréntesis añadiremos los rizados { }. Entre estos paréntesis escribiremos el método:
public int compare(Message o1, Message o2) {
    return o1.getId().compareTo(o2.getId());
}
Ni siquiera tienes que acordarte de escribir esto. Un comparador es aquel que realiza una comparación, es decir, hace una comparación. Para responder a la pregunta de en qué orden están los objetos comparados, devolvemos int. Eso es todo, en realidad. Sencilla y fácilmente. Como podemos ver en el ejemplo, además de Comparator, hay otra interfaz: java.lang.Comparable , cuya implementación debemos definir el método compareTo . Esta interfaz dice que "Una clase que implementa una interfaz permite comparar instancias de la clase". Por ejemplo, la implementación de compareTo de Integer se ve así:
(x < y) ? -1 : ((x == y) ? 0 : 1)
¿Cómo recordar todas estas interfaces? ¿Para qué? Todo viene del inglés. Comparar - para comparar, el que compara es Comparador (como registrador, por ejemplo. Es decir, el que registra), y el adjetivo “comparado” es Comparable. Bueno, “Comparar con” se traduce no sólo como comparar con, sino también como comparar con. Es sencillo. El lenguaje Java fue escrito por personas de habla inglesa, y al nombrar todo en Java se guiaron simplemente por el inglés y había algún tipo de lógica en el nombramiento. Y el método compareTo describe cómo se debe comparar una instancia de una clase con otras instancias. Por ejemplo, las cadenas se comparan lexigráficamente y los números se comparan por valor.
Comparador en Java - 2
Java 8 trajo algunos cambios interesantes. Si nos fijamos bien en la interfaz del Comparador, veremos que encima hay una anotación @FunctionalInterface. De hecho, esta anotación es informativa y significa que esta interfaz es funcional. Esto significa que esta interfaz tiene solo 1 método abstracto sin implementación. ¿Qué nos aporta esto? Podemos escribir el código del comparador ahora así:
Comparator<Message> comparator = (o1, o2) -> o1.getId().compareTo(o2.getId());
Entre paréntesis está cómo nombramos las variables. El propio Java verá eso porque... Si solo hay un método, entonces queda claro qué parámetros de entrada se necesitan, cuántos y de qué tipos. A continuación decimos con una flecha que queremos pasarlos a esta sección del código. Además, gracias a Java 8, aparecieron métodos predeterminados en las interfaces: estos son métodos que aparecen de forma predeterminada (por defecto) cuando implementamos una interfaz. Hay varios de estos en la interfaz del Comparador, por ejemplo:
Comparator moreImportant = Comparator.reverseOrder();
Comparator lessImportant = Comparator.naturalOrder();
Existe otro método que hará que su código sea más limpio. Miremos el ejemplo anterior, donde describimos nuestro comparador. ¿Qué está haciendo? Es bastante primitivo. Simplemente toma un objeto y extrae de él algún valor que sea comparable. Por ejemplo, Integer implementa comparable, por lo que pudimos realizar compareTo en los valores de identificación del mensaje. Esta función de comparación simple también se puede escribir así:
Comparator<Message> comparator = Comparator.comparing(obj -> obj.getId());
Es decir, literalmente, "Tenemos un Comparador que compara así: toma objetos, obtiene Comparable a partir de ellos usando el método getId(), compara usando compareTo". Y no más diseños terribles. Y finalmente, me gustaría 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

La declaración del comparador resultó bastante lógica, ¿no? Ahora falta ver cómo usarlo y en qué lugares. → Collections.sort (java.util.Collections) Por supuesto, podemos ordenar colecciones de esta manera. Pero no todo, sólo listas. Y no hay nada inusual aquí, porque... Es la lista que requiere acceso a un elemento por índice. Y esto permite intercambiar el elemento número dos con el elemento número tres. Por lo tanto, ordenar de esta forma sólo es posible para listas:
Comparator<Message> comparator = Comparator.comparing(obj -> obj.getId());
Collections.sort(messages, comparator);
Arrays.sort (java.util.Arrays) Las matrices también son convenientes para ordenar. Nuevamente, por la misma razón de acceder a elementos por índice. → Descendientes de java.util.SortedSet y java.util.SortedMap Como recordamos, Set y Map no garantizan el orden de almacenamiento de los registros. PERO tenemos implementaciones especiales que garantizan el orden. Y si los elementos de la colección no implementan java.lang.Comparable, entonces podemos pasar Comparator al constructor de dichas colecciones:
Set<Message> msgSet = new TreeSet(comparator);
Stream API En Stream Api, que apareció en Java 8, un comparador le permite simplificar el trabajo en elementos de flujo. Por ejemplo, necesitamos una secuencia de números aleatorios del 0 al 999 inclusive:
Supplier<Integer> randomizer = () -> new Random().nextInt(1000);
Stream.generate(randomizer)
    .limit(10)
    .sorted(Comparator.naturalOrder())
    .forEach(e -> System.out.println(e));
Podríamos parar, pero hay problemas más interesantes. Por ejemplo, necesita preparar un mapa, donde la clave es la identificación del mensaje. Al mismo tiempo, queremos ordenar estas claves para que estén en orden, de menor a mayor. Comencemos con este código:
Map<Integer, Message> collected = Arrays.stream(messages)
                .sorted(Comparator.comparing(msg -> msg.getId()))
                .collect(Collectors.toMap(msg -> msg.getId(), msg -> msg));
Lo que obtendremos aquí es en realidad un HashMap. Y como sabemos, no garantiza ningún pedido. Por lo tanto, nuestros registros ordenados por ID simplemente se desordenaron. No es bueno. Tendremos que cambiar un poco nuestro coleccionista:
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 parecía un poco más espeluznante, pero el problema ahora se resolvió correctamente gracias a la implementación explícita de TreeMap. Puedes leer más sobre los distintos grupos aquí: Puedes crear el recopilador tú mismo. Puede leer más aquí: "Creación de un recopilador personalizado en Java 8" . Y es útil leer la discusión aquí: "Lista de Java 8 para asignar con secuencia" .
Comparador en Java - 3
Los rastrillos comparador y comparable son buenos. Pero hay un matiz asociado con ellos que vale la pena recordar. Cuando una clase realiza una clasificación, calcula que puede convertir su clase en Comparable. Si este no es el caso, recibirá un error en el momento de la ejecución. Veamos un ejemplo:
SortedSet<Message> msg = new TreeSet<>();
msg.add(new Message(2, "Developer".getBytes()));
Parece que aquí no pasa nada. Pero, de hecho, en nuestro ejemplo, fallará con el error: java.lang.ClassCastException: Message cannot be cast to java.lang.Comparable Y todo porque intentó ordenar los elementos (después de todo, es un SortedSet). Y no pude. Debes recordar esto cuando trabajes con SortedMap y SortedSet. Además, se recomienda ver: Yuri Tkach: HashSet y TreeSet - Colecciones n.° 1 - Java avanzado
Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION