Articles

filtrowanie kolekcji Java według listy

przegląd

filtrowanie kolekcji według listy jest typowym scenariuszem logiki biznesowej. Istnieje wiele sposobów, aby to osiągnąć. Jednak niektóre mogą prowadzić do niedostatecznych rozwiązań, jeśli nie zostaną wykonane prawidłowo.

w tym samouczku porównamy niektóre implementacje filtrowania i omówimy ich zalety i wady.

używając For-Each Loop

zaczniemy od najbardziej klasycznej składni, for-each loop.

dla tego i wszystkich innych przykładów w tym artykule użyjemy następującej klasy:

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

użyjemy również następujących metod dla wszystkich przykładów, dla uproszczenia:

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

w naszym przykładzie filtrujemy pierwszą listę pracowników na podstawie drugiej listy z nazwiskami pracowników, aby znaleźć tylko pracowników z tymi konkretnymi nazwami.

teraz zobaczmy tradycyjne podejście – zapętlanie obu list w poszukiwaniu dopasowań:

@Testpublic 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().equals(name)) { filteredList.add(employee); // break; } } } assertThat(filteredList.size(), is(nameFilter.size()));}

jest to prosta składnia, ale jest dość gadatliwa, a właściwie dość nieefektywna. Mówiąc najprościej, iteracje przez iloczyn kartezjański dwóch zbiorów w celu uzyskania naszej odpowiedzi.

nawet dodanie przerwy do wcześniejszego wyjścia będzie nadal powtarzało się w tej samej kolejności, co iloczyn kartezjański w przypadku przeciętnym.

jeśli nazwiemy wielkość listy pracowników n, to nameFilter będzie w kolejności równie duży, dając nam klasyfikację O (n2).

używając strumieni i listy # zawiera

będziemy teraz refaktorować poprzednią metodę, używając lambda, aby uprościć składnię i poprawić czytelność. Użyjmy również metody List # contains jako filtra lambda:

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

używając Stream API, czytelność została znacznie poprawiona, ale nasz kod pozostaje tak samo nieefektywny jak nasza poprzednia metoda, ponieważ nadal iteracyjnie przechodzi przez produkt kartezjański wewnętrznie. Tak więc mamy tę samą klasyfikację O (n2).

używanie strumieni z HashSet

aby poprawić wydajność, musimy użyć metody HashSet#contains. Ta metoda różni się od List#contains tym, że wykonuje wyszukiwanie kodu hashowego, dając nam stałą liczbę operacji:

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

dzięki zastosowaniu Hashsetu wydajność naszego kodu znacznie się poprawiła, nie wpływając jednocześnie na czytelność. Ponieważ HashSet#zawiera biegi w stałym czasie, poprawiliśmy naszą klasyfikację do O (n).

wnioski

w tym krótkim samouczku nauczyliśmy się filtrować kolekcję według listy wartości i wad używania tego, co może wydawać się najprostszą metodą.

zawsze musimy brać pod uwagę wydajność, ponieważ nasz kod może skończyć się uruchomieniem w ogromnych zestawach danych, a problemy z wydajnością mogą mieć katastrofalne konsekwencje w takich środowiskach.

cały kod przedstawiony w tym artykule jest dostępny na Githubie.

zacznij od Spring 5 i Spring Boot 2, Poprzez kurs Learn Spring:

>>sprawdź kurs