Implementar a interface do Comparador
A interface do Comparator pode ser vista em qualquer lugar da biblioteca JDK, desde a pesquisa até a classificação, operações reversas e assim por diante. No Java 8, torna-se uma interface funcional. A vantagem disso é que podemos usar a sintaxe de streaming para implementar o comparador.
Vamos implementar o Comparator de diversas maneiras diferentes para ver o valor da nova sintaxe. Seus dedos vão agradecer. Não ter que implementar classes internas anônimas economiza muitas teclas.
Classificando usando Comparador
O exemplo a seguir usará diferentes métodos de comparação para classificar um grupo de pessoas. Vamos primeiro criar um Person JavaBean.
Copie o código do código da seguinte forma:
classe pública Pessoa {
nome da string final privada;
idade interna final privada;
public Person(final String theName, final int theAge) {
nome = oNome;
idade = aIdade;
}
public String getNome() { nome de retorno }
public int getAge() { return idade }
public int ageDifference(final Pessoa outro) {
idade de retorno - outra idade;
}
string pública paraString() {
return String.format("%s - %d", nome, idade);
}
}
Podemos implementar a interface Comparator através da classe Person, mas desta forma só podemos utilizar um método de comparação. Queremos poder comparar diferentes atributos – como nome, idade ou uma combinação destes. Para realizar a comparação de maneira flexível, podemos usar o Comparator para gerar código relevante quando precisarmos comparar.
Vamos primeiro criar uma lista de Pessoas, cada uma com nome e idade diferentes.
Copie o código do código da seguinte forma:
final List<Pessoa> pessoas = Arrays.asList(
nova Pessoa("João", 20),
nova Pessoa("Sara", 21),
nova Pessoa("Jane", 21),
nova Pessoa("Greg", 35));
Podemos classificar as pessoas em ordem crescente ou decrescente por nome ou idade. O método geral é usar classes internas anônimas para implementar a interface Comparator. Se escrito desta forma, apenas o código mais relevante terá significado e o resto será apenas uma formalidade. O uso de expressões lambda pode focar na essência da comparação.
Vamos primeiro classificá-los por idade, do mais novo ao mais velho.
Agora que temos um objeto List, podemos usar seu método sort() para classificar. No entanto, este método também tem os seus problemas. Este é um método void, o que significa que quando chamarmos este método, a lista mudará. Para reter a lista original, devemos primeiro fazer uma cópia e depois chamar o método sort(). Foi muito esforço. Neste momento temos que recorrer à classe Stream para obter ajuda.
Podemos obter um objeto Stream da Lista e então chamar seu método sorted(). Ele retorna uma coleção classificada em vez de modificar a coleção original. Usando este método, você pode configurar facilmente os parâmetros do Comparador.
Copie o código do código da seguinte forma:
List<Pessoa> crescenteAge =
pessoas.stream()
.sorted((pessoa1, pessoa2) -> pessoa1.ageDifference(pessoa2))
.collect(toList());
printPeople("Classificado em ordem crescente por idade: ",acrescentAge);
Primeiro convertemos a lista em um objeto Stream por meio do método stream(). Em seguida, chame seu método sorted(). Este método aceita um parâmetro Comparator. Como o Comparator é uma interface funcional, podemos passar uma expressão lambda. Finalmente chamamos o método collect e armazenamos os resultados em uma lista. O método collect é um redutor que pode gerar os objetos durante o processo de iteração em um formato ou tipo específico. O método toList() é um método estático da classe Collectors.
O método abstrato compareTo() do Comparator recebe dois parâmetros, que são os objetos a serem comparados, e retorna um resultado do tipo int. Para ser compatível com isso, nossa expressão lambda também recebe dois parâmetros, dois objetos Person, cujos tipos são deduzidos automaticamente pelo compilador. Retornamos um tipo int indicando se os objetos comparados são iguais.
Como queremos ordenar por idade, compararemos as idades dos dois objetos e depois retornaremos o resultado da comparação. Se forem do mesmo tamanho, retorne 0. Caso contrário, um número negativo será retornado se a primeira pessoa for mais jovem e um número positivo será retornado se a primeira pessoa for mais velha.
O método sorted() percorrerá cada elemento da coleção de destino e chamará o Comparator especificado para determinar a ordem de classificação dos elementos. O método de execução do método sorted() é um pouco semelhante ao método reduzir() mencionado anteriormente. O método reduzir() reduz gradualmente a lista a um resultado. O método sorted() classifica pelos resultados da comparação.
Depois de classificarmos, queremos imprimir os resultados, então chamamos um método printPeople(), vamos implementar este método.
Copie o código do código da seguinte forma:
public static void printPeople(
final String mensagem, final List<Pessoa> pessoas) {
System.out.println(mensagem);
pessoas.forEach(System.out::println);
}
Neste método, primeiro imprimimos uma mensagem, depois percorremos a lista e imprimimos cada elemento nela contido.
Vamos chamar o método sorted() para ver como ele classificará as pessoas na lista, da mais nova para a mais velha.
Copie o código do código da seguinte forma:
Classificadas em ordem crescente por idade:
João - 20
Sara - 21
Jane - 21
Greg - 35
Vamos dar uma outra olhada no método sorted() para fazer uma melhoria.
Copie o código do código da seguinte forma:
.sorted((pessoa1, pessoa2) -> pessoa1.ageDifference(pessoa2))
Na expressão lambda passada, simplesmente roteamos esses dois parâmetros - o primeiro parâmetro é usado como alvo de chamada do método ageDifference() e o segundo parâmetro é usado como seu parâmetro. Mas não podemos escrever assim, mas usar um modo de espaço de escritório - ou seja, usar referências de método e deixar o compilador Java fazer o roteamento.
O roteamento de parâmetros usado aqui é um pouco diferente do que vimos antes. Vimos anteriormente que os argumentos são passados como alvos de chamada ou como parâmetros de chamada. Agora, temos dois parâmetros e queremos dividi-los em duas partes, uma como alvo da chamada do método e a segunda como parâmetro. Não se preocupe, o compilador Java lhe dirá: “Eu cuidarei disso”.
Podemos substituir a expressão lambda no método sorted() anterior por um método ageDifference curto e conciso.
Copie o código do código da seguinte forma:
pessoas.stream()
.sorted(Person::ageDifference)
Este código é muito conciso, graças às referências de métodos fornecidas pelo compilador Java. O compilador recebe dois parâmetros de instância de pessoa e usa o primeiro como alvo do método ageDifference() e o segundo como parâmetro do método. Deixamos o compilador fazer esse trabalho em vez de escrever o código diretamente. Ao usar este método, devemos ter certeza de que o primeiro parâmetro é o alvo de chamada do método referenciado e o restante é o parâmetro de entrada do método.
Comparador de reutilização
É fácil classificar as pessoas na lista da mais nova para a mais velha e também é fácil classificar da mais velha para a mais nova. Vamos tentar.
Copie o código do código da seguinte forma:
printPeople("Classificado em ordem decrescente por idade: ",
pessoas.stream()
.sorted((pessoa1, pessoa2) -> pessoa2.ageDifference(pessoa1))
.collect(toList()));
Chamamos o método sorted() e passamos uma expressão lambda, que cabe na interface Comparator, assim como no exemplo anterior. A única diferença é a implementação desta expressão lambda – alteramos a ordem das pessoas a serem comparadas. Os resultados devem ser organizados do mais velho para o mais novo em termos de idade. Vamos dar uma olhada.
Copie o código do código da seguinte forma:
Classificadas em ordem decrescente por idade:
Greg - 35
Sara - 21
Jane - 21
João - 20
Não é preciso muito esforço para apenas mudar a lógica da comparação. Mas não podemos reconstruir esta versão em uma referência de método porque a ordem dos parâmetros não está de acordo com as regras de roteamento de parâmetros para referências de método. O primeiro parâmetro não é usado como alvo de chamada do método, mas como parâmetro de método; Existe uma forma de resolver este problema que também reduz a duplicação de esforços. Vamos ver como fazer isso.
Criamos duas expressões lambda antes: uma é classificar por idade, de pequeno para grande, e a outra é classificar de grande para pequeno. Fazer isso levará à redundância e duplicação de código e violará o princípio DRY. Se quisermos apenas ajustar a ordem de classificação, o JDK fornece um método reverso, que possui um modificador de método especial, padrão. Discutiremos isso no método padrão na página 77. Aqui primeiro usamos o método reversed() para remover redundância.
Copie o código do código da seguinte forma:
Comparador<Pessoa> compareAcrescente =
(pessoa1, pessoa2) -> pessoa1.ageDifference(pessoa2);
Comparador<Pessoa> compareDescendente = compareAcrescente.reversed();
Primeiro criamos um Comparador, compareAscending, para classificar as pessoas por idade, do mais novo ao mais velho. Para reverter a ordem de comparação, em vez de escrever este código novamente, precisamos apenas chamar o método reversed() do primeiro Comparator para obter o segundo objeto Comparator. Nos bastidores do método reversed(), ele cria um comparador para reverter a ordem dos parâmetros comparados. Isso mostra que invertido também é um método de ordem superior – ele cria e retorna uma função sem efeitos colaterais. Usamos esses dois comparadores no código.
Copie o código do código da seguinte forma:
printPeople("Classificado em ordem crescente por idade: ",
pessoas.stream()
.classificado(compareAscendente)
.collect(toList())
);
printPeople("Classificado em ordem decrescente por idade: ",
pessoas.stream()
.classificado (comparar descendente)
.collect(toList())
);
Pode-se ver claramente no código que esses novos recursos do Java8 reduziram bastante a redundância e a complexidade do código, mas os benefícios são muito mais do que esses. Existem infinitas possibilidades esperando para você explorar no JDK.
Já podemos classificar por idade e também é fácil classificar por nome. Vamos classificá-los lexicograficamente por nome. Da mesma forma, só precisamos alterar a lógica na expressão lambda.
Copie o código do código da seguinte forma:
printPeople("Ordenado em ordem crescente por nome: ",
pessoas.stream()
.classificado((pessoa1, pessoa2) ->
pessoa1.getNome().compareTo(pessoa2.getNome()))
.collect(toList()));
Os resultados de saída serão classificados em ordem lexicográfica por nome.
Copie o código do código da seguinte forma:
Classificadas em ordem crescente por nome:
Greg - 35
Jane - 21
João - 20
Sara - 21
Até agora, classificamos por idade ou por nome. Podemos tornar a lógica das expressões lambda mais inteligentes. Por exemplo, podemos classificar por idade e nome ao mesmo tempo.
Vamos escolher a pessoa mais jovem da lista. Podemos primeiro classificar por idade, da menor para a maior, e depois selecionar a primeira nos resultados. Mas isso realmente não funciona. Este método também aceita um Comparator, mas retorna o menor objeto da coleção. Vamos usá-lo.
Copie o código do código da seguinte forma:
pessoas.stream()
.min(Pessoa::Diferença de idade)
.ifPresent(mais novo -> System.out.println("Mais novo: " + mais novo));
Ao chamar o método min(), usamos a referência do método ageDifference. O método min() retorna um objeto Optinal porque a lista pode estar vazia e pode haver mais de uma pessoa mais jovem nela. Em seguida, pegamos a pessoa mais jovem por meio do método ifPrsend() da Optinal e imprimimos suas informações detalhadas. Vamos dar uma olhada na saída.
Copie o código do código da seguinte forma:
Mais novo: João - 20
Exportar o mais antigo também é muito simples. Basta passar esta referência de método para um método max().
Copie o código do código da seguinte forma:
pessoas.stream()
.max(Pessoa::Diferença de idade)
.ifPresent(mais velho -> System.out.println("mais velho: " + mais velho));
Vamos dar uma olhada no nome e na idade do mais velho.
Copie o código do código da seguinte forma:
Mais velho: Greg - 35
Com expressões lambda e referências de métodos, a implementação de comparadores torna-se mais simples e conveniente. O JDK também introduz muitos métodos convenientes para a classe Compararor, permitindo-nos comparar com mais facilidade, como veremos a seguir.
Múltiplas comparações e comparações de streaming
Vamos dar uma olhada nos novos métodos convenientes fornecidos pela interface do Comparator e usá-los para comparar várias propriedades.
Vamos continuar usando o exemplo da seção anterior. Classificando por nome, é isso que escrevemos acima:
Copie o código do código da seguinte forma:
pessoas.stream()
.classificado((pessoa1, pessoa2) ->
person1.getName().compareTo(person2.getName()));
Comparado com o método de escrita da classe interna do século passado, esse método de escrita é simplesmente muito simples. No entanto, isso pode ser simplificado se usarmos algumas funções da classe Comparator. O uso dessas funções pode nos permitir expressar nosso propósito de maneira mais suave. Por exemplo, se quisermos ordenar por nome, podemos escrever:
Copie o código do código da seguinte forma:
função final<Pessoa, String> byName = pessoa -> pessoa.getNome();
pessoas.stream()
.classificado(comparando(porNome));
Neste código importamos o método estático comparando() da classe Comparator. O método comparando() usa a expressão lambda passada para gerar um objeto Comparator. Em outras palavras, é também uma função de ordem superior que aceita uma função como parâmetro de entrada e retorna outra função. Além de tornar a sintaxe mais concisa, esse código também pode expressar melhor o problema real que queremos resolver.
Com ele, múltiplas comparações podem se tornar mais suaves. Por exemplo, o código a seguir comparando por nome e idade diz tudo:
Copie o código do código da seguinte forma:
função final<Pessoa, Inteiro> byAge = pessoa -> pessoa.getAge();
função final<Pessoa, String> byTheirName = pessoa -> pessoa.getName();
printPeople("Classificado em ordem crescente por idade e nome: ",
pessoas.stream()
.classificado(comparando(porIdade).thenComparando(porNome))
.collect(toList()));
Primeiro criamos duas expressões lambda, uma retorna a idade da pessoa especificada e a outra retorna seu nome. Ao chamar o método sorted(), combinamos essas duas expressões para que vários atributos possam ser comparados. O método comparando() cria e retorna um Comparador com base na idade. Em seguida, chamamos o método thenComparing() no Comparador retornado para criar um comparador combinado que compara idade e nome. A saída abaixo é o resultado da classificação primeiro por idade e depois por nome.
Copie o código do código da seguinte forma:
Classificados em ordem crescente por idade e nome:
João - 20
Jane - 21
Sara - 21
Greg - 35
Como você pode ver, a implementação do Comparator pode ser facilmente combinada usando expressões lambda e as novas classes de ferramentas fornecidas pelo JDK. Vamos apresentar os Colecionadores abaixo.