Articles

filtrera en Java-samling med en lista

översikt

filtrera en samling med en lista är ett vanligt affärslogiskt scenario. Det finns många sätt att uppnå detta. Vissa kan dock leda till underpresterande lösningar om de inte görs ordentligt.

i denna handledning jämför vi några filtreringsimplementeringar och diskuterar deras fördelar och nackdelar.

använda en för-varje slinga

vi börjar med den mest klassiska syntaxen, a för-varje slinga.

För detta och alla andra exempel i den här artikeln använder vi följande klass:

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

vi använder också följande metoder för alla exempel, för enkelhetens skull:

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

för vårt exempel filtrerar vi den första listan över anställda baserat på den andra listan med anställdas namn för att bara hitta de anställda med de specifika namn.

Nu, låt oss se det traditionella tillvägagångssättet-looping genom båda listorna letar efter matchningar:

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

detta är en enkel syntax, men det är ganska verbose och faktiskt ganska ineffektivt. Enkelt uttryckt, det itererar genom den kartesiska produkten av de två uppsättningarna för att få vårt svar.

även om du lägger till en paus för att avsluta tidigt kommer det fortfarande att upprepas i samma ordning som en kartesisk produkt i genomsnitt.

om vi kallar storleken på medarbetarlistan n, så kommer nameFilter att vara på ordern lika stor, vilket ger oss en o(n2) klassificering.

använda strömmar och lista#innehåller

vi ska nu refactor den tidigare metoden genom att använda lambdas för att förenkla syntax och förbättra läsbarheten. Låt oss också använda listan#innehåller metod som lambda-filtret:

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

genom att använda Stream API har läsbarheten förbättrats avsevärt, men vår kod förblir lika ineffektiv som vår tidigare metod eftersom den fortfarande itererar genom den kartesiska produkten internt. Således har vi samma o (n2) klassificering.

använda strömmar med HashSet

för att förbättra prestanda måste vi använda HashSet#innehåller-metoden. Denna metod skiljer sig från List#innehåller eftersom den utför en hashkodsökning, vilket ger oss ett konstant antal operationer:

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

genom att använda HashSet har vår kodeffektivitet förbättrats avsevärt utan att påverka läsbarheten. Eftersom HashSet#innehåller körningar i konstant tid har vi förbättrat vår klassificering till O(n).

slutsats

i denna snabba handledning lärde vi oss hur man filtrerar en samling med en lista över värden och nackdelarna med att använda det som kan verka som den enklaste metoden.

Vi måste alltid överväga effektivitet eftersom vår kod kan hamna i enorma datamängder, och prestandaproblem kan få katastrofala konsekvenser i sådana miljöer.

all kod som presenteras i den här artikeln är tillgänglig över på GitHub.

Kom igång med Spring 5 och Spring Boot 2, genom Learn Spring course:

>> kolla in kursen