Filtrage d'une collection Java par une liste

1. Vue d'ensemble

Le filtrage d'une collection par une liste est un scénario de logique métier courant. Il existe de nombreuses façons d'y parvenir. Cependant, certains peuvent conduire à des solutions sous-performantes si elles ne sont pas effectuées correctement.

Dans ce didacticiel, nous comparerons certaines implémentations de filtrage et discuterons de leurs avantages et inconvénients .

2. Utilisation d'une boucle For-Each

Nous allons commencer par la syntaxe la plus classique, une boucle for-each.

Pour cela et tous les autres exemples de cet article, nous utiliserons la classe suivante:

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

Nous utiliserons également les méthodes suivantes pour tous les exemples, par souci de simplicité:

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

Pour notre exemple, nous filtrerons la première liste d' employés en fonction de la deuxième liste avec les noms d' employés pour trouver uniquement les employés avec ces noms spécifiques.

Voyons maintenant l'approche traditionnelle - parcourir les deux listes à la recherche de correspondances:

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

C'est une syntaxe simple, mais elle est assez verbeuse et en fait assez inefficace. En termes simples, il parcourt le produit cartésien des deux ensembles afin d'obtenir notre réponse.

Même l'ajout d'une pause pour sortir tôt itérera toujours dans le même ordre qu'un produit cartésien dans le cas moyen.

Si nous appelons la taille de la liste d'employés n, alors nameFilter sera sur l'ordre tout aussi grand, nous donnant une classification O (n2) .

3. Utilisation des flux et de la liste # contient

Nous allons maintenant refactoriser la méthode précédente en utilisant des lambdas pour simplifier la syntaxe et améliorer la lisibilité . Utilisons également la méthode List # contains comme filtre 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())); }

En utilisant l' API Stream , la lisibilité a été grandement améliorée, mais notre code reste aussi inefficace que notre méthode précédente car il continue à itérer à travers le produit cartésien en interne . Ainsi, nous avons la même classification O (n2) .

4. Utilisation des flux avec HashSet

Pour améliorer les performances, nous devons utiliser la méthode HashSet # contains . Cette méthode diffère de List # contains car elle effectue une recherche de code de hachage , ce qui nous donne un nombre d'opérations à temps constant:

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

En utilisant HashSet, l' efficacité de notre code s'est considérablement améliorée sans affecter la lisibilité. Puisque HashSet # contient des exécutions en temps constant, nous avons amélioré notre classification en O (n).

5. Conclusion

Dans ce rapide tutoriel, nous avons appris à filtrer une collection par une liste de valeurs et les inconvénients de l'utilisation de ce qui peut sembler être la méthode la plus simple.

Nous devons toujours tenir compte de l'efficacité, car notre code peut finir par s'exécuter dans d'énormes ensembles de données et les problèmes de performances peuvent avoir des conséquences catastrophiques dans de tels environnements.

Tout le code présenté dans cet article est disponible à l'adresse over sur GitHub.