Articles

Filtern einer Java-Sammlung nach einer Liste

Übersicht

Das Filtern einer Sammlung nach einer Liste ist ein gängiges Szenario der Geschäftslogik. Es gibt viele Möglichkeiten, dies zu erreichen. Einige können jedoch zu leistungsschwachen Lösungen führen, wenn sie nicht ordnungsgemäß ausgeführt werden.

In diesem Tutorial werden wir einige Filterimplementierungen vergleichen und ihre Vor- und Nachteile diskutieren.

Verwenden einer For-Each-Schleife

Wir beginnen mit der klassischsten Syntax, einer for-each-Schleife.

Für dieses und alle anderen Beispiele in diesem Artikel verwenden wir die folgende Klasse:

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

Der Einfachheit halber verwenden wir auch die folgenden Methoden für alle Beispiele:

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

In unserem Beispiel filtern wir die erste Liste der Mitarbeiter basierend auf der zweiten Liste mit den Namen der Mitarbeiter, um nur die Mitarbeiter mit diesen spezifischen Namen zu finden.

Nun wollen wir uns den traditionellen Ansatz ansehen – beide Listen nach Übereinstimmungen durchsuchen:

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

Dies ist eine einfache Syntax, aber sie ist ziemlich ausführlich und eigentlich ziemlich ineffizient. Einfach ausgedrückt, durchläuft es das kartesische Produkt der beiden Mengen, um unsere Antwort zu erhalten.

Selbst das Hinzufügen einer Pause zum frühen Beenden iteriert im Durchschnitt immer noch in derselben Reihenfolge wie ein kartesisches Produkt.

Wenn wir die Größe der Mitarbeiterliste n nennen, ist nameFilter in der Reihenfolge genauso groß und gibt uns eine O (n2) -Klassifizierung.

Verwenden von Streams und List#contains

Wir werden nun die vorherige Methode mithilfe von Lambdas umgestalten, um die Syntax zu vereinfachen und die Lesbarkeit zu verbessern. Verwenden wir auch die List#contains-Methode als 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()));}

Durch die Verwendung der Stream-API wurde die Lesbarkeit erheblich verbessert, aber unser Code bleibt so ineffizient wie unsere vorherige Methode, da er intern immer noch durch das kartesische Produkt iteriert. Somit haben wir die gleiche O (n2) -Klassifizierung.

Verwenden von Streams mit HashSet

Um die Leistung zu verbessern, müssen wir die HashSet#contains-Methode verwenden. Diese Methode unterscheidet sich von List#contains dadurch, dass sie eine Hash-Code-Suche durchführt, die uns eine konstante Anzahl von Operationen gibt:

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

Durch die Verwendung von HashSet hat sich unsere Codeeffizienz erheblich verbessert, ohne die Lesbarkeit zu beeinträchtigen. Da HashSet#contains in konstanter Zeit ausgeführt wird, haben wir unsere Klassifizierung auf O(n) verbessert.

Fazit

In dieser Kurzanleitung haben wir gelernt, wie man eine Sammlung nach einer Liste von Werten filtert und welche Nachteile die einfachste Methode zu haben scheint.

Wir müssen immer die Effizienz berücksichtigen, da unser Code möglicherweise in riesigen Datensätzen ausgeführt wird und Leistungsprobleme in solchen Umgebungen katastrophale Folgen haben können.

Der gesamte in diesem Artikel vorgestellte Code ist auf GitHub verfügbar.

Erste Schritte mit Spring 5 und Spring Boot 2 durch den Learn Spring-Kurs:

>> SCHAUEN SIE SICH DEN KURS an