リストによるJavaコレクションのフィルタリング

1. 概要

_List_による_Collection_のフィルタリングは、一般的なビジネスロジックシナリオです。 これを達成する方法はたくさんあります。 ただし、適切に行わないと、一部のソリューションがパフォーマンスの低下につながる可能性があります。
このチュートリアルでは、*いくつかのフィルタリング実装を比較し、それらの利点と欠点*について説明します。

2. _For-Each_ループの使用

最も古典的な構文であるfor-eachループから始めます。
*この記事およびこの記事の他のすべての例では、次のクラスを使用します。*
public class Employee {

    private Integer employeeNumber;
    private String name;
    private Integer departmentId;
    //Standard constructor, getters and setters.
}
また、簡単にするために、すべての例で次のメソッドを使用します。
private List<Employee> 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<String> employeeNameFilter() {
    return Arrays.asList("Alice", "Mike", "Bob");
}
この例では、_Employee_の名前を持つ2番目のリストに基づいて_Employees_の最初のリストをフィルター処理し、それらの特定の名前を持つ_Employees_のみを検索します。
では、従来のアプローチを見てみましょう-両方のリストをループして一致を探します:*
@Test
public void givenEmployeeList_andNameFilterList_thenObtainFilteredEmployeeList_usingForEachLoop() {
    List<Employee> filteredList = new ArrayList<>();
    List<Employee> originalList = buildEmployeeList();
    List<String> nameFilter = employeeNameFilter();

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

    assertThat(filteredList.size(), is(nameFilter.size()));
}
これは単純な構文ですが、非常に冗長であり、あまり効率的ではありません。 *ネストループは、2つのセットのデカルト積を効果的に実行しているため、実行時間とリソース使用量が増加します*。

3. Lambdaと_List.contains()_の使用

  • Lambdasを使用して以前のメソッドをリファクタリングし、構文を簡素化して読みやすくします*。 _List.contains()_メソッドを_https://www.baeldung.com/java-stream-filter-lambda [Lambda filter] _として使用してみましょう:

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

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

    assertThat(filteredList.size(), is(nameFilter.size()));
}
_https://www.baeldung.com/java-8-streams-introduction [Stream API] _を使用することで、読みやすさが大幅に改善されましたが、コードはまだデカルト積を実行しているため、以前の方法と同様に非効率のままです。内部的に*。

4. _HashSet_でLambdaを使用する

パフォーマンスを改善するには、_HashSet.contains()_メソッドを使用する必要があります。 *このメソッドは、前の例のようにすべてのリストメンバーをチェックするのではなく、_List.contains()_メソッドとは異なり、_hash code_を計算し、一致する場合はそれを走査します。*
@Test
public void givenEmployeeList_andNameFilterList_thenObtainFilteredEmployeeList_usingLambdaAndHashSet() {
    List<Employee> filteredList;
    List<Employee> originalList = buildEmployeeList();
    Set<String> nameFilterSet = employeeNameFilter().stream().collect(Collectors.toSet());

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

    assertThat(filteredList.size(), is(nameFilterSet.size()));
}
_https://www.baeldung.com/java-hashset [HashSet]、_を使用することにより、読みやすさに影響を与えずにコードの効率が大幅に向上しました。

5. 結論

このクイックチュートリアルでは、値の_List_で_Collection_をフィルター処理する方法と、最も簡単な方法と思われる方法を使用することの欠点を学びました。
コードは膨大なデータセットで実行される可能性があり、そのような環境ではパフォーマンスの問題が壊滅的な結果をもたらす可能性があるため、常に効率を考慮する必要があります。
この記事に記載されているすべてのコードは、https://github.com/eugenp/tutorials/tree/master/core-java-modules/core-java-collections-list-3 [GitHub上]で入手できます。