Рубрики
Без рубрики

Фильтрация коллекции Java по списку

Изучите различные способы фильтрации коллекции в Java на основе значений другого списка

Автор оригинала: baeldung.

1. Обзор

Фильтрация Коллекции по списку является распространенным сценарием бизнес-логики. Существует множество способов добиться этого. Однако некоторые из них могут привести к неэффективным решениям, если они не будут выполнены должным образом.

В этом уроке мы сравним некоторые реализации фильтрации и обсудим их преимущества и недостатки .

2. Использование цикла Для каждого

Мы начнем с самого классического синтаксиса-цикла “для каждого”.

Для этого и всех других примеров в этой статье мы будем использовать следующий класс:

public class Employee {

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

Мы также будем использовать следующие методы для всех примеров, для простоты:

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");
}

В нашем примере мы отфильтруем первый список Сотрудников на основе второго списка с именами Сотрудников , чтобы найти только Сотрудников с этими конкретными именами.

Теперь давайте рассмотрим традиционный подход – перебираем оба списка в поисках совпадений:

@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()));
}

Это простой синтаксис, но он довольно многословен и на самом деле довольно неэффективен. Проще говоря, он перебирает декартово произведение двух множеств , чтобы получить наш ответ.

Даже добавление break для досрочного выхода все равно будет повторяться в том же порядке, что и декартово произведение в среднем случае.

Если мы назовем размер списка сотрудников n, тогда Фильтр имен будет в заказе таким же большим, что даст нам an O(n 2 ) классификация.

3. Использование потоков и списка#содержит

Теперь мы проведем рефакторинг предыдущего метода с помощью лямбд для упрощения синтаксиса и улучшения читаемости . Давайте также используем метод List#contains в качестве лямбда-фильтра :

@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()));
}

С помощью Stream API читаемость была значительно улучшена , но наш код остается таким же неэффективным, как и наш предыдущий метод, потому что он все еще повторяет декартово произведение внутри . Таким образом, мы имеем то же самое O(n 2 ) классификация.

4. Использование потоков с хэш-набором

Для повышения производительности мы должны использовать метод HashSet#contains . Этот метод отличается от List#contains тем, что он выполняет хэш-код поиск, давая нам постоянное количество операций:

@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()));
}

Используя HashSet , эффективность нашего кода значительно улучшилась, не влияя на читаемость. Поскольку HashSet#содержит выполняется в постоянном времени, мы улучшили нашу классификацию до O(n).

5. Заключение

В этом кратком руководстве мы узнали, как фильтровать Коллекцию по списку значений и о недостатках использования того, что может показаться самым простым методом.

Мы всегда должны учитывать эффективность, потому что наш код может в конечном итоге работать в огромных наборах данных, а проблемы с производительностью могут иметь катастрофические последствия в таких средах.

Весь код, представленный в этой статье, доступен на GitHub .