Filtrando uma coleção Java por uma lista

Filtrando uma coleção Java por uma lista

1. Visão geral

Filtrar aCollection porList é um cenário comum de lógica de negócios. Existem muitas maneiras de conseguir isso. No entanto, alguns podem levar a soluções com baixo desempenho, se não forem feitas corretamente.

Neste tutorial,we’ll compare some filtering implementations and discuss their advantages and drawbacks.

2. Usando um loopFor-Each

Começaremos com a sintaxe mais clássica, um loop for-each.

Para este e todos os outros exemplos neste artigo, usaremos a seguinte classe:

public class Employee {

    private Integer employeeNumber;
    private String name;
    private Integer departmentId;
    //Standard constructor, getters and setters.
}

Também usaremos os seguintes métodos para todos os exemplos, para simplificar:

private List buildEmployeeList() {
    return Arrays.asList(
      new Employee(1, "Mike", 1),
      new Employee(2, "John", 1),
      new Employee(3, "Mary", 1),
      new Employee(4, "Joe", 2),
      new Employee(5, "Nicole", 2),
      new Employee(6, "Alice", 2),
      new Employee(7, "Bob", 3),
      new Employee(8, "Scarlett", 3));
}

private List employeeNameFilter() {
    return Arrays.asList("Alice", "Mike", "Bob");
}

Para o nosso exemplo, filtraremos a primeira lista deEmployees com base na segunda lista com nomesEmployee para encontrar apenas osEmployees com esses nomes específicos.

Agora, vamos ver a abordagem tradicional - looping through both lists looking for matches:

@Test
public void givenEmployeeList_andNameFilterList_thenObtainFilteredEmployeeList_usingForEachLoop() {
    List filteredList = new ArrayList<>();
    List originalList = buildEmployeeList();
    List nameFilter = employeeNameFilter();

    for (Employee employee : originalList) {
        for (String name : nameFilter) {
            if (employee.getName().equals(name)) {
                filteredList.add(employee);
                // break;
            }
        }
    }

    assertThat(filteredList.size(), is(nameFilter.size()));
}

Esta é uma sintaxe simples, mas bastante prolixa e, na verdade, bastante ineficiente. Simplificando, éiterates through the Cartesian product of the two sets para obter nossa resposta.

Mesmo a adição de umbreak para sair mais cedo ainda irá iterar na mesma ordem que um produto cartesiano no caso médio.

Se chamarmos o tamanho da lista de funcionários den, , entãonameFilter estará no pedido tão grande, nos dandoan O(n2classification.

3. Usando Streams eList#contains

We’ll now refactor the previous method by using lambdas to simplify syntax and improve readability. Vamos também usar o métodoList#contains comolambda filter:

@Test
public void givenEmployeeList_andNameFilterList_thenObtainFilteredEmployeeList_usingLambda() {
    List filteredList;
    List originalList = buildEmployeeList();
    List nameFilter = employeeNameFilter();

    filteredList = originalList.stream()
      .filter(employee -> nameFilter.contains(employee.getName()))
      .collect(Collectors.toList());

    assertThat(filteredList.size(), is(nameFilter.size()));
}

Ao usar oStream API, a legibilidade foi bastante melhorada, mas nosso código permanece tão ineficiente quanto nosso método anterior porque éstill iterating through the Cartesian product internally. Assim, temos a mesmaO(n2de esclassificação.

4. Usando Streams comHashSet

Para melhorar o desempenho, devemos usar o métodoHashSet#contains. This method differs from List#contains because it performs a hash code lookup, giving us a constant-time number of operations:

@Test
public void givenEmployeeList_andNameFilterList_thenObtainFilteredEmployeeList_usingLambdaAndHashSet() {
    List filteredList;
    List originalList = buildEmployeeList();
    Set nameFilterSet = employeeNameFilter().stream().collect(Collectors.toSet());

    filteredList = originalList.stream()
      .filter(employee -> nameFilterSet.contains(employee.getName()))
      .collect(Collectors.toList());

    assertThat(filteredList.size(), is(nameFilterSet.size()));
}

UsandoHashSet,, a eficiência do nosso código melhorou muito, mas não afetou a legibilidade. Since HashSet#contains runs in constant time, we’ve improved our classification to O(n).

5. Conclusão

Neste tutorial rápido, aprendemos como filtrarCollection porList dos valores e as desvantagens de usar o que pode parecer o método mais simples.

Sempre devemos considerar a eficiência, pois nosso código pode acabar sendo executado em grandes conjuntos de dados, e os problemas de desempenho podem ter consequências catastróficas nesses ambientes.

Todo o código apresentado neste artigo está disponívelover on GitHub.