terça-feira, 27 de agosto de 2019

Ordenação de listas no Java 8

Nesse artigo iremos abordar as possíveis formas de ordenar listas no Java 8.

Ordenando lista de objetos que não implementam Comparable

Para começarmos a trabalhar com ordenação, primeiramente vamos criar uma lista de produtos:

Product product1 = new Product(4, "Notebook Samsung", new BigDecimal("1200.00"));
Product product2 = new Product(3, "Notebook Dell", new BigDecimal("1200.00"));
Product product3 = new Product(2, "TV", new BigDecimal("3100.00"));
Product product4 = new Product(1, "Sofá", new BigDecimal("2500.00"));

List<Product> products = Arrays.asList(product1, product2, product3, product4);

Acima temos uma lista de produtos, e a classe Product não implementa Comparable, portanto precisamos criar uma implementação de Comparator para fazer a ordenação. Antes do Java 8 podíamos passar para o método Collections.sort a lista a ser ordenada juntamente com a implementação de Comparator:

Comparator<Product> comparator = new Comparator<>() {
 @Override
 public int compare(Product p1, Product p2) {
  return p1.getName().compareTo(p2.getName());
 }
};

Collections.sort(products, comparator);

Uma das variações do código acima, mas agora fazendo uso do Java 8, é utilizar uma expressão lambda no lugar de classe anônima para gerar uma implementação de Comparator, já que ela é uma interface funcional:

Comparator<Product> comparator = (p1, p2) -> p1.getName().compareTo(p2.getName());
Collections.sort(products, comparator);

Simplificando em uma única linha temos:

Collections.sort(products, (p1, p2) -> p1.getName().compareTo(p2.getName()));

Na versão 8 do Java também foi incluído um método default chamado sort na interface List. Podemos utilizá-lo no lugar do Collections.sort, basta invocá-lo diretamente a partir da nossa lista de produtos:

products.sort((p1, p2) -> p1.getName().compareTo(p2.getName()));

As novidades nessa versão não param por ai, ainda foram adicionados alguns métodos estáticos na interface Comparator que funcionam como uma factory de Comparators. Por exemplo podemos utilizar o Comparator.comparing que recebe uma Function e devolve um Comparator:

Function<Product, String> extractName = p -> p.getName();
products.sort(Comparator.comparing(extractName));

O código acima foi quebrado em duas linhas para fins didáticos e de clareza de código, mas podemos simplificá-lo em uma única linha da seguinte forma:

products.sort(Comparator.comparing(p -> p.getName()));

Visando um código mais fácil de entender que favoreça a manutenibilidade, podemos passar para o comparing um method reference(outro recurso introduzido na versão 8 da linguagem) no lugar de uma expressão lambda:

products.sort(Comparator.comparing(Product::getName));

A respeito do method reference, pode ser usado no lugar de uma expressão lambda quando a mesma não faz nada além de chamar um único método existente, como nesse caso que chama apenas p.getName(). Nesses casos geralmente é sempre mais claro chamar o método existente pelo nome através de um method reference. Podemos dizer que os method references são um tipo especial de expressão lambda que são mais fáceis de ler.

Ordenando listas de objetos que implementam Comparable

Abaixo temos uma lista de Strings. Como String implementa Comparable podemos ordenar uma lista de Strings facilmente usando Collections.sort, o que não é nenhuma novidade pois era a forma usada até a versão anterior do Java:

List<String> series = Arrays.asList("Friends", "Prison Break", "Breaking Bad", "Black Mirror");
Collections.sort(series);

O código acima produz a seguinte saída:

[Black Mirror, Breaking Bad, Friends, Prison Break]

Podemos reescrever o código acima utilizando o método default chamado sort que se encontra presente em List na versão 8 do Java, porém é necessário que passemos um Comparator. Um Comparator para ordenar em ordem natural pode ser conseguido através de Comparator.naturalOrder():

series.sort(Comparator.naturalOrder());

O código acima produz a seguinte saída:

[Black Mirror, Breaking Bad, Friends, Prison Break]

Para ordenar na ordem inversa usamos Comparator.reverseOrder():

series.sort(Comparator.reverseOrder());

O código acima produz a seguinte saída:

[Prison Break, Friends, Breaking Bad, Black Mirror]

Evitando autoboxing desnecessário no sort

No código abaixo a ordenação é feita pelo id do produto que é do tipo primitivo int. Dividimos o código em múltiplas linhas com fins didáticos para melhor entendimento. Tivemos que criar uma Function que contém um método apply que irá receber um Product e retornar um Integer ao invés de int. Por conta disso irá ocorrer autoboxing toda vez que esse método for invocado, e ele pode ser invocado muitas vezes durante o sort. Veja:

Function<Product, Integer> extractId = p -> p.getId();
Comparator<Product> comparator = Comparator.comparing(extractId);
products.sort(comparator);

Para evitar autoboxing desnecessário podemos utilizar ToIntFunction ao invés de Function e também fazer uso de Comparator.comparingInt no lugar de Comparator.comparing:

ToIntFunction<Product> extractId = p -> p.getId();
Comparator<Product> comparator = Comparator.comparingInt(extractId);
products.sort(comparator);

O objetivo do artigo era dar um panorama geral das possibilidades mais comuns de ordenação de listas usando Java 8, mais do que ser uma referência extensiva. Espero que tenham gostado.

Para quem quiser executar os códigos do artigo, estão disponíveis no GitHub.

Pô-pô-por hoje é só pe-pe-pessoal! (os mais velhos pegaram a referência a Looney Tunes hahaha)

Nenhum comentário:

Postar um comentário