Articles

Een Java-collectie filteren op basis van een lijst

overzicht

Een verzameling filteren op basis van een lijst is een veelvoorkomend business logic scenario. Er zijn tal van manieren om dit te bereiken. Sommige kunnen echter leiden tot onderpresterende oplossingen als ze niet goed worden gedaan.

in deze tutorial zullen we enkele filterimplementaties vergelijken en hun voor-en nadelen bespreken.

met een voor-elke lus

beginnen we met de meest klassieke syntaxis, een voor-elke lus.

voor dit en alle andere voorbeelden in dit artikel zullen we de volgende Klasse gebruiken:

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

We zullen ook de volgende methoden gebruiken voor alle voorbeelden, omwille van de eenvoud:

voor ons voorbeeld zullen we de eerste lijst van werknemers filteren op basis van de tweede lijst met Werknemersnamen om alleen de werknemers met die specifieke namen te vinden.

laten we nu eens kijken naar de traditionele aanpak-looping door beide lijsten op zoek naar overeenkomsten:

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

Dit is een eenvoudige syntaxis, maar het is vrij uitgebreid, en eigenlijk vrij inefficiënt. Simpel gezegd, het itereert door het Cartesiaanse product van de twee verzamelingen om ons antwoord te krijgen.

zelfs het toevoegen van een pauze om vroeg af te sluiten zal nog steeds herhalen op dezelfde volgorde als een Cartesiaans product in het gemiddelde geval.

als we de grootte van de werknemerslijst n noemen, dan zal nameFilter op de volgorde net zo groot zijn, wat ons een O(n2) classificatie geeft.

gebruikmakend van Streams en List#bevat

We zullen nu de vorige methode herschrijven door lambdas te gebruiken om de syntaxis te vereenvoudigen en de leesbaarheid te verbeteren. Laten we ook de List#bevat methode gebruiken als het 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()));}

door het gebruik van de Stream API is de leesbaarheid sterk verbeterd, maar onze code blijft even inefficiënt als onze vorige methode omdat het nog steeds intern door het Cartesiaanse product itereert. We hebben dus dezelfde o (n2) classificatie.

Streams met HashSet

gebruiken om de prestaties te verbeteren, moeten we de HashSet#bevat methode gebruiken. Deze methode verschilt van lijst#bevat omdat het een hash code lookup uitvoert, wat ons een constant-time aantal bewerkingen geeft:

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

door het gebruik van HashSet is onze code-efficiëntie enorm verbeterd zonder de leesbaarheid te beïnvloeden. Omdat HashSet#runs in constante tijd bevat, hebben we onze classificatie verbeterd naar O(n).

conclusie

In deze korte handleiding hebben we geleerd hoe je een verzameling filtert op basis van een lijst met waarden en de nadelen van het gebruik van wat misschien de meest eenvoudige methode lijkt.

We moeten altijd rekening houden met efficiëntie omdat onze code kan eindigen in enorme datasets, en prestatieproblemen kunnen catastrofale gevolgen hebben in dergelijke omgevingen.

alle code in dit artikel is beschikbaar op GitHub.

aan de slag met Spring 5 en Spring Boot 2, Via de learn Spring course:

>> bekijk de cursus