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.
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 pelaMessage
classe:
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 main
mé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 Object
classe. 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 hashCode
mé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 hashCode
s 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 instance
s diferentes, deve haver hashCode
s diferentes. Ou seja, esse método não é adequado para nossa comparação. → equals
. O equals
mé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 equals
mé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.Comparator
interface 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 main
mé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 compareTo
método. Essa interface diz: "uma classe que me implementa torna possível comparar instâncias da classe". Por exemplo, Integer
a implementação de compare
To é a seguinte:
(x < y) ? -1 : ((x == y) ? 0 : 1)
O Java 8 introduziu algumas mudanças interessantes. Se você observar a Comparator
interface mais de perto, verá a @FunctionalInterface
anotaçã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 Comparator
interface 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, Integer
implementa 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 Comparator
que compara assim: pega objetos, usa o getId()
método para obter a Comparable
deles e depois usa compareTo
para 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 Set
e Map
nã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 Comparator
para 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 HashMap
aqui. 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
Comparator
e Comparable
sã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 SortedMap
e SortedSet
.
Mais leitura: |
---|
GO TO FULL VERSION