Articles

filtrering af en Java-samling efter en liste

oversigt

filtrering af en samling efter en liste er et almindeligt forretningslogikscenarie. Der er mange måder at opnå dette på. Nogle kan dog føre til underpresterende løsninger, hvis de ikke udføres korrekt.

i denne vejledning sammenligner vi nogle filtreringsimplementeringer og diskuterer deres fordele og ulemper.

brug af en for-hver sløjfe

vi begynder med den mest klassiske syntaks, a for-hver sløjfe.

til dette og alle andre eksempler i denne artikel bruger vi følgende klasse:

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

Vi bruger også følgende metoder til alle eksempler for enkelhedens skyld:

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

for vores eksempel filtrerer vi den første liste over medarbejdere baseret på den anden liste med medarbejdernavne for kun at finde de ansatte med de specifikke navne.

lad os nu se den traditionelle tilgang-looping gennem begge lister på udkig efter kampe:

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

Dette er en simpel syntaks, men det er ret verbose og faktisk ret ineffektivt. Kort sagt, det gentager sig gennem det kartesiske produkt af de to sæt for at få vores svar.

selv tilføjelse af en pause for at afslutte tidligt vil stadig gentage i samme rækkefølge som et kartesisk produkt i det gennemsnitlige tilfælde.

Hvis vi kalder størrelsen på medarbejderlisten n, så navnefilter vil være på ordren lige så stor, hvilket giver os en O(n2) klassificering.

brug af Streams og List#indeholder

Vi refactorerer nu den tidligere metode ved at bruge lambdas til at forenkle syntaks og forbedre læsbarheden. Lad os også bruge listen#indeholder metode som lambda filter:

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

Ved at bruge Stream API er læsbarheden blevet forbedret betydeligt, men vores kode forbliver lige så ineffektiv som vores tidligere metode, fordi den stadig gentager gennem det kartesiske produkt internt. Således har vi den samme o (n2) klassificering.

brug af Streams med HashSet

for at forbedre ydeevnen skal vi bruge HashSet#indeholder metode. Denne metode adskiller sig fra List#indeholder, fordi den udfører et hash-kodeopslag, hvilket giver os et 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()));}

Ved at bruge HashSet er vores kodeeffektivitet meget forbedret, mens den ikke påvirker læsbarheden. Da HashSet # indeholder kørsler i konstant tid, har vi forbedret vores klassificering til O(n).

konklusion

i denne hurtige tutorial lærte vi, hvordan man filtrerer en samling efter en liste over værdier og ulemperne ved at bruge det, der kan virke som den mest ligefremme metode.

Vi skal altid overveje effektivitet, fordi vores kode kan ende med at køre i store datasæt, og ydelsesproblemer kan have katastrofale konsekvenser i sådanne miljøer.

al kode, der præsenteres i denne artikel, er tilgængelig på GitHub.

kom i gang med Spring 5 og Spring Boot 2, gennem Lær Spring course:

>> tjek kurset