JavaRush /Blogue Java /Random-PT /Comparador em Java
Viacheslav
Nível 3

Comparador em Java

Publicado no grupo Random-PT
Somente pessoas preguiçosas não escreveram sobre Comparador e comparação em Java. Não sou preguiçoso - por isso peço que amem e favoreçam mais uma variação. Espero que não seja supérfluo. E sim, este artigo é a resposta à pergunta: “Você consegue escrever um comparador de memória?” Espero que depois de ler este artigo todos consigam escrever um comparador de memória.
Comparador em Java - 1
Introdução Java é conhecido por ser uma linguagem orientada a objetos. Como resultado, em Java é comum operar com objetos. Mas, mais cedo ou mais tarde, surge a tarefa de comparar objetos de acordo com algum princípio. Então, dado: Temos alguma mensagem, que é descrita pela classe Message:
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;
    }
}
Vamos adicionar esta classe ao compilador Java Tutorialspoint . Lembremos também de adicionar importações:
import java.util.Random;
import java.util.ArrayList;
import java.util.List;
No método principal criaremos diversas mensagens:
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);
}
Vamos pensar no que devemos fazer se quisermos compará-los? Por exemplo, queremos classificar por id. E para criar ordem, você precisa comparar objetos de alguma forma para entender qual objeto é o anterior (ou seja, menor) e qual é o próximo (ou seja, maior). Vamos começar com uma classe como java.lang.Object . Como sabemos, todas as classes herdam implicitamente desta classe Object. E isso é lógico, porque Isto expressa essencialmente o conceito: "Tudo é um objeto" e fornece um comportamento comum para todas as classes. E esta classe define que cada classe possui dois métodos: → hashCode O método hashCode retorna alguma representação numérica (int) do objeto como uma instância da classe. O que isso significa? Isso significa que se você criou duas instâncias diferentes de uma classe, como as instâncias são diferentes, seu hashCode deverá ser diferente. Isto é o que diz na descrição do método: “Por mais que seja razoavelmente prático, o método hashCode definido pela classe Object retorna números inteiros distintos para objetos distintos” Ou seja, se estas são duas instâncias diferentes, então elas devem ter diferentes códigos hash. Ou seja, este método não é adequado para nossa comparação. → equals O método equals responde à pergunta “os objetos são iguais” e retorna um booleano. Este método possui o código padrão:
public boolean equals(Object obj) {
    return (this == obj);
}
Ou seja, sem substituir este método em um objeto, este método diz essencialmente se as referências ao objeto correspondem ou não. Isso não é adequado para nossas mensagens, pois não estamos interessados ​​em links para o objeto, estamos interessados ​​no id da mensagem. E mesmo se substituirmos o método equals, o máximo que obteremos será: “Eles são iguais” ou “Eles não são iguais”. Mas isso não é suficiente para determinarmos a ordem.

Comparador e comparável em Java

O que nos convém? Se traduzirmos a palavra “comparar” para o inglês no tradutor, obteremos a tradução “comparar”. Ótimo, então precisamos de alguém que compare. Se você comparar esse compara, então quem compara é o Comparador. Vamos abrir o Java Api e encontrar o Comparator lá . E, de fato, existe essa interface - java.util.Comparator java.util.Comparator e java.lang.Comparable Como você pode ver, existe essa interface. A classe que o implementa diz: “Estou implementando uma função para comparar objetos”. A única coisa a realmente lembrar é o contrato comparador, expresso da seguinte forma:

Comparator возвращает int по следующей схеме: 
  • отрицательный int (первый an object отрицательный, то есть меньше)
  • положительный int (первый an object положительный, хороший, то есть больший)
  • ноль = an objectы равны
Agora vamos escrever um comparador. Precisaremos importar java.util.Comparator . Após a importação, adicione um método ao main: Comparator<Message> comparator = new Comparator<Message>(); Naturalmente, isso não funcionará, porque Comparador é uma interface. Portanto, após os parênteses adicionaremos os encaracolados { }. Nestes colchetes escreveremos o método:
public int compare(Message o1, Message o2) {
    return o1.getId().compareTo(o2.getId());
}
Você nem precisa se lembrar de escrever isso. Comparador é aquele que realiza uma comparação, ou seja, faz um comparativo. Para responder à questão de qual ordem os objetos comparados estão, retornamos int. Isso é tudo, na verdade. Simples e fácil. Como podemos ver no exemplo, além do Comparator, existe outra interface - java.lang.Comparable , implementando a qual devemos definir o método compareTo . Esta interface diz que "Uma classe que implementa uma interface permite que instâncias da classe sejam comparadas." Por exemplo, a implementação de compareTo em Integer é assim:
(x < y) ? -1 : ((x == y) ? 0 : 1)
Como lembrar de todas essas interfaces? Pelo que? Tudo vem do inglês. Comparar - para comparar, quem compara é Comparador (como registrador, por exemplo. Ou seja, quem registra), e o adjetivo “comparado” é Comparável. Bem, “Comparar com” é traduzido não apenas como comparar com, mas também como comparar com. É simples. A linguagem Java foi escrita por pessoas que falam inglês e, ao nomear tudo em Java, eles foram guiados simplesmente pelo inglês e havia algum tipo de lógica na nomenclatura. E o método compareTo descreve como uma instância de uma classe deve ser comparada com outras instâncias. Por exemplo, strings são comparadas lexigraficamente e números são comparados por valor.
Comparador em Java - 2
Java 8 trouxe algumas mudanças interessantes. Se olharmos atentamente para a interface do Comparador, veremos que há uma anotação acima dela @FunctionalInterface. Na verdade, esta anotação é informativa e significa que esta interface é funcional. Isso significa que esta interface possui apenas 1 método abstrato sem implementação. O que isso nos dá? Podemos escrever o código comparador agora assim:
Comparator<Message> comparator = (o1, o2) -> o1.getId().compareTo(o2.getId());
Entre parênteses está como nomeamos as variáveis. O próprio Java verá isso porque. Se houver apenas um método, ficará claro quais parâmetros de entrada são necessários, quantos e quais tipos. A seguir, dizemos com uma seta que queremos transferi-los para esta seção do código. Além disso, graças ao Java 8, métodos padrão apareceram nas interfaces - são métodos que aparecem por padrão (por padrão) quando implementamos uma interface. Existem vários deles na interface do Comparador. Por exemplo:
Comparator moreImportant = Comparator.reverseOrder();
Comparator lessImportant = Comparator.naturalOrder();
Existe outro método que tornará seu código mais limpo. Vejamos o exemplo acima, onde descrevemos nosso comparador. O que ele está fazendo? É bastante primitivo. Ele simplesmente pega um objeto e extrai dele algum valor que seja comparável. Por exemplo, Integer implementa comparável, então pudemos executar compareTo em valores de id de mensagem. Esta função comparadora simples também pode ser escrita assim:
Comparator<Message> comparator = Comparator.comparing(obj -> obj.getId());
Isto é, literalmente, “Temos um Comparador que compara assim: ele pega objetos, obtém Comparable deles usando o método getId(), compara usando compareTo”. E chega de designs terríveis. E, finalmente, gostaria de observar mais uma característica. Os comparadores podem ser encadeados. Por exemplo:
Comparator<Message> comparator = Comparator.comparing(obj -> obj.getId());
comparator = comparator.thenComparing(obj -> obj.getMessage().length());

Aplicativo

A declaração do comparador revelou-se bastante lógica, não foi? Agora precisamos ver como usá-lo e em que locais. → Collections.sort (java.util.Collections) Claro, podemos classificar as coleções desta forma. Mas nem tudo, apenas listas. E não há nada de incomum aqui, porque... É a lista que requer acesso a um elemento por índice. E isso permite que o elemento número dois seja trocado pelo elemento número três. Portanto, a classificação desta forma só é possível para listas:
Comparator<Message> comparator = Comparator.comparing(obj -> obj.getId());
Collections.sort(messages, comparator);
Arrays.sort (java.util.Arrays) Arrays também são convenientes para classificar. Novamente, pelo mesmo motivo de acessar elementos por índice. → Descendentes de java.util.SortedSet e java.util.SortedMap Como lembramos, Set e Map não garantem a ordem de armazenamento dos registros. MAS temos implementações especiais que garantem a ordem. E se os elementos da coleção não implementarem java.lang.Comparable, então podemos passar o Comparator para o construtor de tais coleções:
Set<Message> msgSet = new TreeSet(comparator);
Stream API Na Stream Api, que apareceu no Java 8, um comparador permite simplificar o trabalho em elementos de stream. Por exemplo, precisamos de uma sequência de números aleatórios de 0 a 999 inclusive:
Supplier<Integer> randomizer = () -> new Random().nextInt(1000);
Stream.generate(randomizer)
    .limit(10)
    .sorted(Comparator.naturalOrder())
    .forEach(e -> System.out.println(e));
Poderíamos parar, mas há problemas mais interessantes. Por exemplo, você precisa preparar um Mapa, onde a chave é o id da mensagem. Ao mesmo tempo, queremos classificar essas chaves para que fiquem em ordem, da menor para a maior. Vamos começar com este código:
Map<Integer, Message> collected = Arrays.stream(messages)
                .sorted(Comparator.comparing(msg -> msg.getId()))
                .collect(Collectors.toMap(msg -> msg.getId(), msg -> msg));
O que receberemos aqui é na verdade um HashMap. E como sabemos, não garante nenhum pedido. Portanto, nossos registros classificados por ID simplesmente ficaram fora de ordem. Não é bom. Teremos que mudar um pouco nosso coletor:
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));
O código parecia um pouco mais assustador, mas o problema agora foi resolvido corretamente graças à implementação explícita do TreeMap. Você pode ler mais sobre os vários grupos aqui: Você mesmo pode criar o coletor. Você pode ler mais aqui: "Criando um coletor customizado em Java 8" . E é útil ler a discussão aqui: "Java 8 list to map with stream" .
Comparador em Java - 3
Rakes Comparadores e Comparáveis ​​são bons. Mas há uma nuance associada a eles que vale a pena lembrar. Quando uma classe realiza a classificação, ela calcula que pode converter sua classe em Comparable. Caso contrário, você receberá um erro em tempo de execução. Vejamos um exemplo:
SortedSet<Message> msg = new TreeSet<>();
msg.add(new Message(2, "Developer".getBytes()));
Parece que não há nada de errado aqui. Mas na verdade, no nosso exemplo, ele irá travar com o erro: java.lang.ClassCastException: Message cannot be cast to java.lang.Comparable E tudo porque tentou ordenar os elementos (afinal, é um SortedSet). E eu não consegui. Você deve se lembrar disso ao trabalhar com SortedMap e SortedSet. Adicionalmente recomendado para visualização: Yuri Tkach: HashSet e TreeSet - Coleções #1 - Java Avançado
Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION