Articles

Filtrare una raccolta Java in base a un elenco

Panoramica

Filtrare una raccolta in base a un elenco è uno scenario di logica aziendale comune. Ci sono molti modi per raggiungere questo obiettivo. Tuttavia, alcuni possono portare a soluzioni poco performanti se non eseguite correttamente.

In questo tutorial, confronteremo alcune implementazioni di filtraggio e discuteremo i loro vantaggi e svantaggi.

Usando un ciclo For-Each

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

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

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

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

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

Per il nostro esempio, ti filtrare il primo elenco dei Dipendenti, in base alla seconda lista con i nomi dei Dipendenti per trovare solo i Dipendenti con quei nomi specifici.

Ora, vediamo l’approccio tradizionale-looping attraverso entrambe le liste alla ricerca di corrispondenze:

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

Questa è una sintassi semplice, ma è piuttosto dettagliata 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 nello stesso ordine di un prodotto cartesiano nel caso medio.

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

Utilizzando Streams e List#contiene

Ora rifattorizzeremo il metodo precedente utilizzando lambda per semplificare la sintassi e migliorare la leggibilità. Usiamo anche il metodo List # contains come filtro lambda:

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

Utilizzando l’API Stream, la leggibilità è stata notevolmente migliorata, ma il nostro codice rimane inefficiente come il nostro metodo precedente perché continua a scorrere internamente il prodotto cartesiano. Quindi, abbiamo la stessa classificazione O(n2).

Utilizzando i flussi con HashSet

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

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

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

Conclusione

In questo breve tutorial, abbiamo imparato come filtrare una raccolta da un elenco di valori e gli svantaggi di utilizzare quello che può sembrare il metodo più semplice.

Dobbiamo sempre considerare l’efficienza perché il nostro codice potrebbe finire in esecuzione 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.

Inizia con Spring 5 e Spring Boot 2, attraverso il corso Learn Spring:

>>DAI UN’OCCHIATA AL CORSO