Filtraggio di una raccolta Java tramite un elenco

1. Panoramica

Il filtraggio di una raccolta in base a un elenco è uno scenario di logica aziendale comune. Ci sono molti modi per ottenere questo risultato. Tuttavia, alcuni possono portare a soluzioni con prestazioni insufficienti se non eseguite correttamente.

In questo tutorial, confronteremo alcune implementazioni di filtri e ne discuteremo i vantaggi e gli svantaggi .

2. Utilizzo di un ciclo For-Each

Inizieremo con la sintassi più classica, un ciclo for-each.

Per questo e tutti gli altri esempi in questo articolo, useremo la seguente classe:

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

Useremo anche i seguenti metodi per tutti gli esempi, per semplicità:

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

Per il nostro esempio, filtreremo il primo elenco di dipendenti in base al secondo elenco con i nomi dei dipendenti per trovare solo i dipendenti con quei nomi specifici.

Ora, vediamo l'approccio tradizionale: scorrere entrambe le liste alla ricerca di corrispondenze:

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

Questa è una sintassi semplice, ma è piuttosto prolissa e in realtà piuttosto inefficiente. In poche parole, itera attraverso il prodotto cartesiano dei due set per ottenere la nostra risposta.

Anche l'aggiunta di un'interruzione per uscire in anticipo continuerà a iterare sullo stesso ordine di un prodotto cartesiano nel caso medio.

Se chiamiamo la dimensione della lista dei dipendenti n, allora nameFilter sarà dell'ordine altrettanto grande, dandoci un (n2) O classificazione.

3. Utilizzo di flussi e elenco # contiene

Effettueremo ora il refactoring del metodo precedente utilizzando espressioni lambda per semplificare la sintassi e migliorare la leggibilità . Usiamo anche il metodo List # contains come filtro lambda :

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

Utilizzando l' API Stream , la leggibilità è stata notevolmente migliorata, ma il nostro codice rimane inefficiente come il nostro metodo precedente perché sta ancora iterando internamente attraverso il prodotto cartesiano . Quindi, abbiamo la stessa classificazione O (n2) .

4. Utilizzo di stream con HashSet

Per migliorare le prestazioni, dobbiamo utilizzare il metodo HashSet # contains . Questo metodo differisce da List # contains perché esegue una ricerca di codice hash , dandoci un numero di operazioni nel tempo costante:

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

Utilizzando HashSet, la nostra efficienza del codice è notevolmente migliorata senza influire sulla leggibilità. Poiché HashSet # contiene esecuzioni a tempo costante, abbiamo migliorato la nostra classificazione in O (n).

5. conclusione

In questo rapido tutorial, abbiamo imparato come filtrare una raccolta in base a un elenco di valori e gli svantaggi dell'utilizzo di quello che può sembrare il metodo più semplice.

Dobbiamo sempre considerare l'efficienza perché il nostro codice potrebbe finire per essere eseguito in enormi set di dati e problemi di prestazioni potrebbero avere conseguenze catastrofiche in tali ambienti.

Tutto il codice presentato in questo articolo è disponibile su GitHub.