Филтриране на колекция Java по списък

1. Общ преглед

Филтрирането на колекция от списък е често срещан сценарий на бизнес логика. Има много начини да постигнете това. Някои обаче могат да доведат до недостатъчно ефективни решения, ако не бъдат направени правилно.

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

2. Използване на For-Every Loop

Ще започнем с най-класическия синтаксис, цикъл за всеки.

За този и всички други примери в тази статия ще използваме следния клас:

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

Това е прост синтаксис, но е доста подробен и всъщност доста неефективен. Най-просто казано, той итерира през декартовия продукт на двата набора, за да получим нашия отговор.

Дори добавянето на почивка за ранно излизане все пак ще повтори в същия ред като декартовия продукт в средния случай.

Ако извикаме размера на списъка на служителите n, тогава nameFilter ще бъде в поръчката също толкова голям, което ни дава класификация O (n2) .

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 (n2) .

4. Използване на потоци с HashSet

За да подобрим производителността, трябва да използваме метода HashSet # 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.