CodeGym /Blogue Java /Random-PT /Interface do comparador do Java
John Squirrels
Nível 41
San Francisco

Interface do comparador do Java

Publicado no grupo Random-PT
Os preguiçosos não são os únicos a escrever sobre comparadores e comparações em Java. Eu não sou preguiçoso, então, por favor, ame e reclame sobre mais uma explicação. Espero que não seja supérfluo. E sim, este artigo é a resposta para a pergunta: " Você pode escrever um comparador de memória? " Espero que todos consigam escrever um comparador de memória depois de ler este artigo. Interface do comparador de Javas - 1

Introdução

Como você sabe, Java é uma linguagem orientada a objetos. Como resultado, é comum manipular objetos em Java. Mas, mais cedo ou mais tarde, você se depara com a tarefa de comparar objetos com base em alguma característica. Por exemplo : Suponha que temos alguma mensagem descrita pela Messageclasse:

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 classe no compilador Tutorialspoint Java . Não se esqueça de adicionar também as instruções de importação:

import java.util.Random;
import java.util.ArrayList;
import java.util.List;
No mainmétodo, crie várias 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 faríamos se quiséssemos compará-los? Por exemplo, queremos classificar por id. E para criar uma ordem, precisamos de alguma forma comparar os objetos para entender qual objeto deve vir primeiro (ou seja, o menor) e qual deve vir depois (ou seja, o maior). Vamos começar com uma classe como java.lang.Object . Sabemos que todas as classes herdam implicitamente a Objectclasse. E isso faz sentido porque reflete o conceito de que "tudo é um objeto" e fornece um comportamento comum para todas as classes. Esta classe dita que toda classe tem dois métodos: → hashCode O hashCodemétodo retorna algum valor numérico (int) representação do objeto. O que isso significa? Isso significa que, se você criar duas instâncias diferentes de uma classe, elas deverão ter hashCodes diferentes. A descrição do método diz o seguinte: "Tanto quanto é razoavelmente prático, o método hashCode definido pela classe Object retorna inteiros distintos para objetos distintos". Em outras palavras, para dois instances diferentes, deve haver hashCodes diferentes. Ou seja, esse método não é adequado para nossa comparação. → equals. O equalsmétodo responde à pergunta "esses objetos são iguais?" e retorna um boolean." Por padrão, esse método possui o seguinte código:

public boolean equals(Object obj) {
    return (this == obj);
}
Ou seja, se este método não for substituído, ele basicamente diz se as referências do objeto correspondem ou não. Isso não é o que queremos para nossas mensagens, porque estamos interessados ​​em IDs de mensagem, não em referências de objeto. E mesmo que passemos por cima do equalsmétodo, o máximo que podemos esperar é saber se são iguais. E isso não é suficiente para determinarmos a ordem. Então, o que precisamos? Precisamos de algo que compare. Quem compara é um Comparator. Abra a API Java e encontre o Comparator . De fato, existe uma java.util.Comparatorinterface java.util.Comparator and java.util.Comparable Como você pode ver, essa interface existe. Uma classe que o implementa diz: "Eu implemento um método que compara objetos". A única coisa que você realmente precisa lembrar é o contrato comparador, que é expresso da seguinte forma:

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
Agora vamos escrever um comparador. Teremos que importar java.util.Comparator. Após a declaração de importação, adicione o seguinte ao mainmétodo: Comparator<Message> comparator = new Comparator<Message>(); Claro, isso não funcionará, porque Comparatoré uma interface. Portanto, adicionamos chaves {}após os parênteses. Escreva o seguinte método dentro das chaves:

public int compare(Message o1, Message o2) {
    return o1.getId().compareTo(o2.getId());
}
Você nem precisa se lembrar da ortografia. Um comparador é aquele que realiza uma comparação, ou seja, compara. Para indicar a ordem relativa dos objetos, retornamos um int. Basicamente é isso. Legal e fácil. Como você pode ver no exemplo, além do Comparator, existe outra interface — java.lang.Comparable, que nos obriga a implementar o compareTométodo. Essa interface diz: "uma classe que me implementa torna possível comparar instâncias da classe". Por exemplo, Integera implementação de compareTo é a seguinte:

(x < y) ? -1 : ((x == y) ? 0 : 1)
O Java 8 introduziu algumas mudanças interessantes. Se você observar a Comparatorinterface mais de perto, verá a @FunctionalInterfaceanotação acima dela. Esta anotação é para fins informativos e nos diz que esta interface é funcional. Isso significa que esta interface possui apenas 1 método abstrato, que é um método sem implementação. O que isso nos dá? Agora podemos escrever o código do comparador assim:

Comparator<Message> comparator = (o1, o2) -> o1.getId().compareTo(o2.getId());
Nomeamos as variáveis ​​entre parênteses. Java verá que, como há apenas um método, o número necessário e os tipos de parâmetros de entrada são claros. Em seguida, usamos o operador de seta para passá-los para esta parte do código. Além disso, graças ao Java 8, agora temos métodos padrão nas interfaces. Esses métodos aparecem por padrão quando implementamos uma interface. A Comparatorinterface tem vários. Por exemplo:

Comparator moreImportant = Comparator.reverseOrder();
Comparator lessImportant = Comparator.naturalOrder();
Existe outro método que tornará seu código mais limpo. Dê uma olhada no exemplo acima, onde definimos nosso comparador. O que isso faz? É bastante primitivo. Ele simplesmente pega um objeto e extrai algum valor que seja "comparável". Por exemplo, Integerimplementa comparable, portanto, podemos executar uma operação compareTo nos valores dos campos de id da mensagem. Esta simples função de comparação pode ser escrita assim:

Comparator<Message> comparator = Comparator.comparing(obj -> obj.getId());
Em outras palavras, temos a Comparatorque compara assim: pega objetos, usa o getId()método para obter a Comparabledeles e depois usa compareTopara comparar. E não há construções mais horríveis. E, finalmente, quero observar mais um recurso. Os comparadores podem ser encadeados. Por exemplo:

Comparator<Message> comparator = Comparator.comparing(obj -> obj.getId());
comparator = comparator.thenComparing(obj -> obj.getMessage().length());

Aplicativo

Declarar um comparador acaba sendo bastante lógico, não acha? Agora precisamos ver como e onde usá-lo. → Collections.sort(java.util.Collections) Podemos, é claro, classificar as coleções dessa maneira. Mas nem todas as coleções, apenas listas. Não há nada incomum aqui, porque as listas são o tipo de coleção em que você acessa os elementos por seu índice. Isso permite que o segundo elemento seja trocado pelo terceiro elemento. É por isso que o seguinte método de classificação é apenas para listas:

Comparator<Message> comparator = Comparator.comparing(obj -> obj.getId());
Collections.sort(messages, comparator);
Arrays.sort(java.util.Arrays) Arrays também são fáceis de classificar. Novamente, pelo mesmo motivo — seus elementos são acessados ​​por index. → Descendants of java.util.SortedSet and java.util.SortedMap Você se lembrará disso Sete Mapnão garante a ordem em que os elementos são armazenados. MAS, temos implementações especiais que garantem o pedido. E se os elementos de uma coleção não implementarem java.util.Comparable, podemos passar a Comparatorpara seu construtor:

Set<Message> msgSet = new TreeSet(comparator);
Stream API Na Stream API, que apareceu no Java 8, os comparadores permitem simplificar o trabalho com elementos de stream. Por exemplo, suponha que 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 por aqui, mas há problemas ainda mais interessantes. Por exemplo, suponha que você precise preparar um Map, onde a chave é um id de mensagem. Além disso, queremos classificar essas chaves, então começaremos com o seguinte código:

Map<Integer, Message> collected = Arrays.stream(messages)
                .sorted(Comparator.comparing(msg -> msg.getId()))
                .collect(Collectors.toMap(msg -> msg.getId(), msg -> msg));
Na verdade, temos um HashMapaqui. E como sabemos, não garante nenhum pedido. Como resultado, nossos elementos, que foram classificados por id, simplesmente perdem a 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 começou a parecer um pouco mais assustador, mas agora o problema foi resolvido corretamente. Leia mais sobre os vários agrupamentos aqui: Você pode criar seu próprio coletor. Leia mais aqui: "Criando um coletor personalizado no Java 8" . E você se beneficiará lendo a discussão aqui: "Java 8 list to map with stream" .

armadilha de queda

Comparatore Comparablesão bons. Mas há uma nuance que você deve se lembrar. Quando uma classe executa a classificação, ela espera que sua classe possa ser convertida em um arquivo Comparable. Se não for esse o caso, você receberá um erro no tempo de execução. Vejamos um exemplo:

SortedSet<Message> msg = new TreeSet<>();
msg.add(new Message(2, "Developer".getBytes()));
Parece que nada está errado aqui. Mas, na verdade, em nosso exemplo, ele falhará com um erro: java.lang.ClassCastException: Message cannot be cast to java.lang.Comparable E tudo porque tentou classificar os elementos (é um SortedSet, afinal)... mas não conseguiu. Não se esqueça disso ao trabalhar com SortedMape SortedSet.
Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION